django/djangorestframework で JOIN する 親から子、子から親
https://qiita.com/uturned0/items/973b32be719a52947f3c の続き
background
GETはrelationしてるobjectを全部、POSTは relationsしているobjectのpkのみを受け取る、というのをしようとして↓を勉強。ありがとう。
やってみると、 update のときに item_id が None になる問題にハマっていた. 理解するためにまず non-relation で勉強
modelはこれ
class Item(models.Model):
name = models.CharField(max_length=100)
tel = models.CharField(max_length=100)
def __str__(self):
return self.name
serializer
class ItemSerializer(serializers.ModelSerializer):
class Meta:
model = Item
fields = ['id', 'name', 'tel']
def create(self, request):
return Item.objects.create(**request)
def update(self, instance, request):
item_id = request.pop('item_id', None)
if item_id:
# ここで適当な処理がしたい的な要望
instance.item = Item.objects.all(pk=item_id)
return super().update(instance, request)
DRFよくあるエラー
"detail": "Method \"PUT\" not allowed."
"detail": "Method \"PATCH\" not allowed."
request URL が list/ になってて、 list/1 のようにupdate対象のpkを指定していない
custom field を POST で受け取りたい場合
DRFのcreate() にくる request はvalidated_data なので、不要なdictは来ない
model/serializerにいない post requestを受け付けるには以下のunnecessaryな行が必要
class ItemSerializer(serializers.ModelSerializer):
unnecessary = serializers.CharField(write_only=True) # <-- ここに書くと request に含まれる対象として認識される
class Meta:
model = Item
fields = ['id', 'name', 'type', 'unnecessary'] # <-------------
def create(self, request):
unnecessary = request.pop("unnecessary") # <-- するとここにデータが入ってくる。modelsにはないfieldなので、 pop でrequestから削除するのが必須
return Item.objects.create(**request)
fields / create / update ... の不思議
write_onlyを指定することによりこのフィールドをGET時には出さないようにしておきます。
fields にはget/post関わらず使う可能性のあるものをすべて書く。
postのときはあると困る・・とか考えなくていい。
write_only / read_only がそのスイッチで、DRFがいい感じに fields を解釈してくれる。
read_only = GET only
write_only = only POST, PATCH, PUT, DELETE と思えばよさそう
requestにやってくるデータの型はserializerのclass変数で来まる
CharField = str
class ItemSerializer(serializers.ModelSerializer):
item_id = serializers.CharField( #<---------- CharField = str()
write_only=True,
)
class Meta:
model = Item
fields = ['id', 'name', 'tel']
def create(self, request):
item_id = request.get('item_id', None) #<--- str で値が入る
return Item.objects.create(job_history=item_id, **request)
UUIDField = uuid()
class ItemSerializer(serializers.ModelSerializer):
item_id = serializers.UUIDField( #<------------- UUID
write_only=True,
)
...
def create(self, request):
item_id = request.get('item_id', None) # uuid型になる
...
ハマった理由
fieldの指定をrelation系のものにすると、relationが見つからない場合は None になるようだ
class ItemSerializer(serializers.ModelSerializer):
parrent_id = serializers.PrimaryKeyRelatedField(
とか
parrent_id = serializers.StringsRelatedField(
とか
,,,
def create(self, request):
parrent_id = request.get('parrent_id', None) # Noneになる
...
見つかった場合は、instance object が入る。
def create(self, request):
parrent = request.get('parrent_id', None)
print(parrent.pk) # instanceの中に各fieldが入ってくる
...
serializer の relation を理解する
わからないこと: PrimaryKeyRelatedField って、pkとpkをつないだあと、何を返すの?
親を返すの?子を返すの? どっち?
manual
ここによると、PrimaryKeyRelatedField は親で使っている。子で使うとどうなるんだ?
親 → 子 relation
class Album(models.Model):
album_name = models.CharField(max_length=100)
artist = models.CharField(max_length=100)
class Track(models.Model):
album = models.ForeignKey(Album, related_name='tracks', on_delete=models.CASCADE)
order = models.IntegerField()
title = models.CharField(max_length=100)
duration = models.IntegerField()
class Meta:
unique_together = ['album', 'order']
ordering = ['order']
def __str__(self):
return '%d: %s' % (self.order, self.title)
class AlbumSerializer(serializers.ModelSerializer):
tracks = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
class Meta:
model = Album
fields = ['album_name', 'artist', 'tracks']
class TrackSerializer(serializers.ModelSerializer):
class Meta:
model = Track
fields = ['order', 'title', 'duration']
python manage.py makemigrations
python manage.py migrate
python manage.py shell
import django
django.setup()
from myapp.models import *
from myapp.serializers import *
album = Album.objects.create(album_name="The Grey Album", artist='Danger Mouse')
Track.objects.create(album=album, order=1, title='Public Service Announcement', duration=245)
Track.objects.create(album=album, order=2, title='What More Can I Say', duration=264)
Track.objects.create(album=album, order=3, title='Encore', duration=159)
serializer = AlbumSerializer(instance=album)
serializer.data
{
"album_name": "The Grey Album",
"artist": "Danger Mouse",
"tracks": [
1,
2,
3
]
}
子のpkが入りました。
子 → 親 relation
class AnotherAlbum(models.Model):
album_name = models.CharField(max_length=100)
artist = models.CharField(max_length=100)
class AnotherTrack(models.Model):
album = models.ForeignKey(AnotherAlbum, related_name='tracks', on_delete=models.CASCADE)
order = models.IntegerField()
title = models.CharField(max_length=100)
duration = models.IntegerField()
class Meta:
unique_together = ['album', 'order']
ordering = ['order']
def __str__(self):
return '%d: %s' % (self.order, self.title)
class AnotherAlbumSerializer(serializers.ModelSerializer):
#ここをやめて tracks = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
class Meta:
model = AnotherAlbum
fields = ['album_name', 'artist'
# , 'tracks'
]
class AnotherTrackSerializer(serializers.ModelSerializer):
album = serializers.PrimaryKeyRelatedField(read_only=True) # 子に追加
class Meta:
model = AnotherTrack
fields = ['order', 'title', 'duration', 'album']
python manage.py makemigrations
python manage.py migrate
python manage.py shell
import django
django.setup()
from myapp.models import *
from myapp.serializers import *
album = AnotherAlbum.objects.create(album_name="The Grey Album", artist='Danger Mouse')
AnotherTrack.objects.create(album=album, order=1, title='Public Service Announcement', duration=245)
AnotherTrack.objects.create(album=album, order=2, title='What More Can I Say', duration=264)
AnotherTrack.objects.create(album=album, order=3, title='Encore', duration=159)
serializer = AnotherAlbumSerializer(instance=album)
serializer.data
[
{
"order": 1,
"title": "Public Service Announcement",
"duration": 245,
"album": 1
},
{
"order": 2,
"title": "What More Can I Say",
"duration": 264,
"album": 1
},
{
"order": 3,
"title": "Encore",
"duration": 159,
"album": 1
}
]
親のpkが入りました。
結論
PrimaryKeyRelatedField は 親に使えば子のpkを返すし、子に使えば親のpkを返す。
結果、どうなったか
relationしてる子をcreateするとき、parent objectを取るのをやめた。
create() では UUIDField でただの変数として取得。
その値を使って親のobjectを取得、 Item.object.create(parent=parent, **request) するようにした。
Noneになるよってエラーより、このほうがjoin先のpkが見つからないエラーもわかりやすかった。