5
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

django/djangorestframework で JOIN する 親から子、子から親

Last updated at Posted at 2021-04-02

DRFで Many-to-one relationships しようとしてよくわからなくなったので復習した。

git

今回のソースはこちら

結論

  1. models で ForeignKey する
  2. serializers で JOIN する
  3. view は 何もしない

viewでやると思ってたのでずっこけた。

難しいところ

modelが Foreignkeyしてる時点で、django的にはserializerで何もしなくてもJOINされてる。コレは不正確かもしれないが、そう感じる。ただ、 JOINされることと、それを出力するかどうかは別問題。
serializerの方で、明示的にそれを出力する指示が必要。そこが謎の関数群だった。
viewはもらったもの出すだけなので、何もしなくていかった。

django 単体でやってみる

manual

mkdir join
cd join

  pip install django
  django-admin startproject mysite
  cd mysite
  python manage.py runserver
  ctrl + c

  django-admin startapp myapp

DB に 親子テーブルをつくる

子供の方に ForeignKey があるのがポイントです。

myapp/models.py
from django.db import models

class Manufacturer(models.Model):
    name = models.CharField(max_length=100)

    def __str__(self):
        return self.name

class Car(models.Model):
    manufacturer = models.ForeignKey(Manufacturer, on_delete=models.CASCADE)
    name = models.CharField(max_length=100)

    def __str__(self):
        return self.name
 python manage.py makemigrations
 python manage.py migrate

DB にデータを入れる

以下の親子関係を作ります。

nissan
  |
  --- leaf 

django shell でやってみましょう。

 python manage.py shell
>>> import django
>>> django.setup()         <-----ここまで決り文句
>>>
>>> from myapp.models import *
>>> Manufacturer.objects.all()
<QuerySet []>               # まだ空ですね

# 親を入れる

>>> m = Manufacturer.objects.create(name="nissan")
>>> m.save()
>>> m = Manufacturer.objects.get(name="nissan")
>>> m
<Manufacturer: nissan>

# 子を入れる

>>> c = Car(name="leaf", manufacturer=m)
>>> c.save()
>>> Car.objects.get(name="leaf")
<Car: leaf>

# 実はもうrelationしている

>>> Car.objects.get(name="leaf").manufacturer.name
'nissan'

子から親が取れました。

django rest framework でやってみる

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

pip install djangorestframework
python manage.py createsuperuser --email admin@example.com --username admin

models に related_name='cars', が必要になります。

myapp/models.py
from django.db import models

class Manufacturer(models.Model):
    name = models.CharField(max_length=100)

    def __str__(self):
        return self.name

class Car(models.Model):
    manufacturer = models.ForeignKey(Manufacturer,
                                     related_name='cars',       # <--------
                                     on_delete=models.CASCADE)
    name = models.CharField(max_length=100)

    def __str__(self):
        return self.name

無心の呼吸で準備する

先にview作ります。無心でコピペしてください。

myapp/views.py
from django.shortcuts import render

# Create your views here.
from rest_framework import viewsets
from .serializers import *

class ManufacturerViewSet(viewsets.ModelViewSet):
    queryset = Manufacturer.objects.all()
    serializer_class = ManufacturerSerializer

class CarViewSet(viewsets.ModelViewSet):
    queryset = Car.objects.all()
    serializer_class = CarSerializer

先に URL もつけときましょう。無心でコピペしてください。

myapp.urls.py
from django.urls import include, path
from rest_framework import routers
from .views import *

router = routers.DefaultRouter()
router.register(r'manufacturer', ManufacturerViewSet)
router.register(r'car', CarViewSet)

# Wire up our API using automatic URL routing.
# Additionally, we include login URLs for the browsable API.
urlpatterns = [
    path('api-auth/', include('rest_framework.urls', namespace='rest_framework')),
    path('', include(router.urls)),
]

myappのurlsを本体に読み込ませます。無心でコピペしてください。

mysite.urls.py
from django.contrib import admin

from django.urls import include, path
from rest_framework import routers
# Wire up our API using automatic URL routing.
# Additionally, we include login URLs for the browsable API.
urlpatterns = [
    path('', include('myapp.urls')),
    path('admin/', admin.site.urls),
]

JOINしてみよう

selializerで、表示する親子関係を指定します。viewじゃなくてselializerでやります。

relationしないバージョン

まず、relationしないselializerです。

myapp/serializers.py
from .models import *
from rest_framework import serializers

class ManufacturerSerializer(serializers.ModelSerializer):
    class Meta:
        model = Manufacturer
        fields = ['name']


class CarSerializer(serializers.ModelSerializer):
    class Meta:
        model = Car
        fields = ['name']

results

単純に、そのテーブルの内容だけが見えます

[
    {
        "name": "nissan"
    },
    {
        "name": "honda"
    }
]

[
    {
        "name": "leaf"
    },
    {
        "name": "n-box"
    }
]

実は見えないだけでrelationしている

selializerの fields に 対向のmodel名を入れると、実はpkが紐付いているのがわかります。

myapp/serializers.py
class ManufacturerSerializer(serializers.ModelSerializer):
    class Meta:
        model = Manufacturer
        fields = ['name', 'cars']


class CarSerializer(serializers.ModelSerializer):
    class Meta:
        model = Car
        fields = ['name', 'manufacturer']

results

pkのidだけが見えます。ここで name が引けなくて悩んだ。

[
    {
        "name": "nissan",
        "cars": [
            1
        ]
    },
    {
        "name": "honda",
        "cars": [
            2
        ]
    }
]

[
    {
        "name": "leaf",
        "manufacturer": 1
    },
    {
        "name": "n-box",
        "manufacturer": 2
    }
]

join しましょう

それをどうやってjoinして表示するかと言うと・・DRFの魔法を使います。まったく意味はわからないです。
そういうもんだと思うしかない。

myapp/serializers.py
from .models import *
from rest_framework import serializers

class ManufacturerSerializer(serializers.ModelSerializer):
    cars = serializers.StringRelatedField(many=True)          # <------- これが親 → 子
    class Meta:
        model = Manufacturer
        fields = ['name', 'cars']


class CarSerializer(serializers.ModelSerializer):
    manufacturer = ManufacturerSerializer()         # <------- これが 子 → 親
    class Meta:
        model = Car
        fields = ['name', 'manufacturer']

results

親から子

[
    {
        "name": "nissan",
        "cars": [
            "leaf"
        ]
    },
    {
        "name": "honda",
        "cars": [
            "n-box"
        ]
    }
]

子から親

[
    {
        "name": "leaf",
        "manufacturer": {
            "name": "nissan",
            "cars": [
                "leaf"
            ]
        }
    },
    {
        "name": "n-box",
        "manufacturer": {
            "name": "honda",
            "cars": [
                "n-box"
            ]
        }
    }
]

原理

ちょっと待て、 StringRelatedField ってどのカラムにでも聴くんか?
これ、実はmodel の __str__ に依存してます。

def __str__(self):
    return self.name <---ここでnameを指定してるからJOINできた

じゃあ動的にそれを変えるには?ってのはまだ検索してません。誰かコメントよろ。

まとめ

親 → 子

    cars = serializers.StringRelatedField(many=True)       

これはわからないだろ。

子 → 親

    manufacturer = ManufacturerSerializer()   

もっとわからないだろ。

5
3
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
5
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?