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

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

# git

今回のソースはこちら

https://github.com/kujiy/django-join

# 結論
1. models で ForeignKey する
1. serializers で JOIN する
1. view は 何もしない

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

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



# django 単体でやってみる

manual

https://docs.djangoproject.com/en/3.1/topics/db/models/


```ruby
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 があるのがポイントです。

```py: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
```


```ruby
 python manage.py makemigrations
 python manage.py migrate
```

## DB にデータを入れる

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


```py
nissan
  |
  --- leaf 
```

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

```ruby
 python manage.py shell
```

```py
>>> 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/


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

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


```py: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作ります。無心でコピペしてください。

```py: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 もつけときましょう。無心でコピペしてください。

```py: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を本体に読み込ませます。無心でコピペしてください。

```py: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です。


```py: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

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

http://127.0.0.1:8000/manufacturer/

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


http://127.0.0.1:8000/cars/

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

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

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

```py: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

http://127.0.0.1:8000/manufacturer/

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

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


http://127.0.0.1:8000/cars/

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

## join しましょう

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


```py: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

親から子

http://127.0.0.1:8000/manufacturer/


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

子から親

http://127.0.0.1:8000/cars/

```json
[
    {
        "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できた

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


# まとめ

親 → 子

```py
    cars = serializers.StringRelatedField(many=True)       
```

これはわからないだろ。


子 →　親

```py
    manufacturer = ManufacturerSerializer()   
```


もっとわからないだろ。