1
1

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 rest framwork で request.get() が取れない場合. PrimaryKeyRelatedField とか

Last updated at Posted at 2021-04-05

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

models.py
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)
serializers.py
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']

input-data
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
result-of-album
{
    "album_name": "The Grey Album",
    "artist": "Danger Mouse",
    "tracks": [
        1,
        2,
        3
    ]
}

子のpkが入りました。

子 → 親 relation

models.py
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)
serializers.py
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']
input-data
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
result-of-track
[
    {
        "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が見つからないエラーもわかりやすかった。

1
1
0

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
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?