見出し画像

DRF_ListSerializerが全然うまくいかない話 #169日目

今回はただただ上手くいかない話についてです。

Django REST Framework (以下、DRF) の学習を開始しまして、早速躓いております。DRFでは通常のDjangoと違って、シリアライズ (モデルオブジェクトをJSONに変換)やデシリアライズ (JSONをモデルオブジェクトに変換) という工程が必要なのですが、この手法のうち一つが上手くいっていません。

上手くいっていないのは「ListSerializer」というクラスを継承してシリアライズする方法です。

シリアライズのスタンダードな方法は「ModelSerializer」というクラスを継承する手法ですが、そちらはデータを1件ずつしかPOSTできません。ListSerializerを使うと、複数のデータをまとめてPOSTできるらしく、試してみようと試行錯誤していますがエラーが解消できていない状況です。

まだ糸口は見えないものの、発生しているエラーが大きく2種類ありそうです。全て同じプロジェクトに紐づいている3種類のアプリを作って検証しました。

エラーの経験こそ大事な資産だと思うので、本日はコードの中身とエラー内容をそれぞれ整理したいと思います。

AttributeError: 'XXXXListSerializer' object has no attribute 'YYYY'

まずはAttributeErrorが発生しているパターンについてです。これは2種類のコードで発生しました。

まずは 'fields' という属性が無いよ、というエラーが発生したパターンです。

third_drf_projectはアプリ名

核となるmodels.py, serializers.py, views.py, urls.pyは以下です。

[models.py]
from django.db import models
from django.contrib.auth.models import User

class Post(models.Model):
    title      = models.CharField(max_length=50)
    content    = models.TextField()
    user       = models.ForeignKey(User, on_delete=models.CASCADE)
    like       = models.ManyToManyField(User, related_name='related_post', blank=True)
    created_at = models.DateTimeField(auto_now_add=True)
[serializers.py]
from rest_framework import serializers
from .models import Post

class PostSerializer(serializers.ModelSerializer):
    class Meta:
        model  = Post
        fields = ['title', 'content', 'user', 'like']

class PostListSerializer(serializers.ListSerializer):
    child = PostSerializer()
[views.py]
from rest_framework import viewsets
from .models import Post
from .serializers import PostSerializer, PostListSerializer


# こっちはエラーなしで意図通り動作した
class PostViewSet(viewsets.ModelViewSet):
    queryset = Post.objects.all()
    serializer_class = PostSerializer

# こっちが「'PostListSerializer' object has no attribute 'fields'」となった
class PostListViewSet(viewsets.ModelViewSet):
    queryset = Post.objects.all()
    serializer_class = PostListSerializer
[urls.py]
from django.urls import path, include
from rest_framework import routers
from .views import PostViewSet, PostListViewSet

# ルーター(URLの自動登録機能を備えたインスタンス)を作成し、/posts系のAPIを一気に作成
router = routers.DefaultRouter()
router.register('post', PostViewSet)
router.register('post2', PostListViewSet)

urlpatterns = [
    path('', include(router.urls)),
]


次に、同じAttributeErrorですが 'get' という属性が無いよ、というエラーが発生したパターンです。

first_drf_projectはアプリ名

核となるmodels.py, serializers.py, views.py, urls.pyは以下です

[models.py]
from django.db import models

class SampleModel(models.Model):
    title       = models.CharField(max_length = 100)
    description = models.CharField(max_length = 300)

    def __str__(self):
        return self.title
[serializers.py]
from rest_framework import serializers                 # APIの出力をJSON,XMLデータに変換
from .models import SampleModel

class SampleSerializer(serializers.ModelSerializer):
    class Meta:
        model  = SampleModel                           # 呼び出すモデル
        fields = ["id", "title", "description"]        # API上に表示するモデルのデータ項目

class SampleListSerializer(serializers.ListSerializer):
    child = SampleSerializer()
[views.py]
from rest_framework import viewsets
from .models import SampleModel
from .serializers import SampleListSerializer

# こっちが「'SampleListSerializer' object has no attribute 'get'」となった
class SampleListViewSet(viewsets.ModelViewSet):
    queryset = SampleModel.objects.all()
    serializer_class = SampleListSerializer
[urls.py]
from django.urls import path
from . import views, apis

urlpatterns = [
    path('', views.SampleListSerializer, name="sample"),
    # 以下はここでは無関係のパス
    path('api1/<int:pk>', apis.api.as_view(), name = "api"),
    path('api2/', apis.api2.as_view(), name = "api2"),
    path('api3/', apis.api3.as_view(), name = "api2"),
]



TypeError: 'モデル名' object is not iterable

2種類目のエラーはTypeErrorです。なぜか指定したモデルはイテラブルじゃないよ、というエラーが出ています。。こちらも2種類のコードで発生しました。

まずはここではAPIViewで作成したパターンです。ここまでは全てModelViewSetクラスを継承して作成してきてましたが、少し違ったパターンでの検証になります。

first_drf_projectはアプリ名

核となるmodels.py, serializers.py, api.py, urls.pyは以下です

[models.py]
from django.db import models

class SampleModel(models.Model):
    title       = models.CharField(max_length = 100)
    description = models.CharField(max_length = 300)

    def __str__(self):
        return self.title
[serializers.py]
from rest_framework import serializers                 # APIの出力をJSON,XMLデータに変換
from .models import SampleModel

class SampleSerializer(serializers.ModelSerializer):
    class Meta:
        model  = SampleModel                           # 呼び出すモデル
        fields = ["id", "title", "description"]        # API上に表示するモデルのデータ項目

class SampleListSerializer(serializers.ListSerializer):
    child = SampleSerializer()
[api.py]
from .models import SampleModel
from rest_framework.generics import ListCreateAPIView, RetrieveAPIView
from .serializers import SampleListSerializer, SampleSerializer


# これはエラーなしで意図通り動作した
class api(RetrieveAPIView):
    queryset           = SampleModel.objects.all()
    serializer_class   = SampleSerializer
    permission_classes = []

# これはエラーなしで意図通り動作した
class api2(ListCreateAPIView):
    queryset           = SampleModel.objects.all()   # 対象とするモデルのオブジェクトを定義(この名前じゃないと動かない)
    serializer_class   = SampleSerializer            # APIがデータを返すためのデータ変換ロジックを定義(この名前じゃないと動かない)
    permission_classes = []                          # 認証。アクセス権を付与したい場合はここに設定する(必須ではないが使う場合は多分この名前じゃないと動かない)

# これが「'SampleModel' object is not iterable」となった
class api3(ListCreateAPIView):
    queryset           = SampleModel.objects.all()   
    serializer_class   = SampleListSerializer        
    permission_classes = []                          
[urls.py]
from django.urls import path
from . import views, apis

urlpatterns = [
    path('', views.SampleListSerializer, name="sample"),  # これは今回の例には無関係 
    path('api1/<int:pk>', apis.api.as_view(), name = "api"),
    path('api2/', apis.api2.as_view(), name = "api2"),
    path('api3/', apis.api3.as_view(), name = "api2"),
]


最後のパターンは再びModelViewSetクラスを継承したものです。こちらも上記事例と同様にモデルのオブジェクトがイテラブルではない、というエラーが出ました。

second_drf_projectはアプリ名

核となるmodels.py, serializers.py, views.py, urls.pyは以下です

[models.py]
from django.db import models

class UserInfo(models.Model):
    user_name  = models.CharField(verbose_name='ユーザー名', max_length=32)
    birth_day  = models.DateField(verbose_name='生年月日')
    age        = models.PositiveSmallIntegerField(verbose_name='年齢', null=True, unique=False)
    created_at = models.DateTimeField(verbose_name='作成日時', auto_now_add=True)
[serializers.py]
from rest_framework import serializers
from .models import UserInfo

class UserInfoSerializer(serializers.ModelSerializer):
    class Meta:
        model  = UserInfo
        fields = "__all__"

class UserInfoListSerializer(serializers.ListSerializer):
    child = UserInfoSerializer()
[views.py]
from rest_framework import viewsets
from .models import UserInfo
from .serializers import UserInfoSerializer, UserInfoListSerializer


# こっちはエラーなしで意図通り動作した
class UserInfoViewSet(viewsets.ModelViewSet):
    queryset         = UserInfo.objects.all()
    serializer_class = UserInfoSerializer
 
# こっちが「'UserInfo' object is not iterable」となった
class UserInfoListViewSet(viewsets.ModelViewSet):
    queryset         = UserInfo.objects.all()
    serializer_class = UserInfoListSerializer
[urls.py]
from django.urls import path, include
from rest_framework import routers
from . import views

defaultRouter = routers.DefaultRouter()
defaultRouter.register('userInfo', views.UserInfoViewSet)
defaultRouter.register('userInfo2', views.UserInfoListViewSet)

urlpatterns = [
    path('', include(defaultRouter.urls), name="userinfo"),
]



ずっとスタックしていてもしょうがないので、一旦これは置いておいて先に進みます。知識が深まってくれば解決できる可能性もアップすると思うので、、

ここまでお読みいただきありがとうございました!

いいなと思ったら応援しよう!