DRFで Many-to-one relationships しようとしてよくわからなくなったので復習した。
git
今回のソースはこちら
結論
- models で ForeignKey する
- serializers で JOIN する
- 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 があるのがポイントです。
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',
が必要になります。
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作ります。無心でコピペしてください。
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 もつけときましょう。無心でコピペしてください。
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を本体に読み込ませます。無心でコピペしてください。
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です。
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が紐付いているのがわかります。
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の魔法を使います。まったく意味はわからないです。
そういうもんだと思うしかない。
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()
もっとわからないだろ。