DRFの小ネタとか


この記事は Django Advent Calendar 2017 13日目の記事です。

DjangoRestFrameWorkの初心者向けの記事になります。
DRF便利ですよね。でも日本語の資料がなかったりして英語ばっかり読むの辛いですし、こんな時にどうしよう?みたいな悩みがちょくちょく出てくると思います。

そんな時の業務で得た豆知識集を書いていこうと思います。
正直。ググれば公式やstackoverflowなんかにでてくるネタではありますしベストな回答かどうかはわかりませんが。。。
前提としては主にModelSerializerの話をします

Selializerのfieldsから必要な項目だけ抽出したい

apiによって欲しい項目というものは当然異なってきます。
とはいえModelは広大になっていき全項目を返しているとデータ転送量はふくれあがるし、若干重くもなります。
だからといって用途毎Selializerを作っていたら XXMiniselializerとかXXsnipetSelializerとかよく分からないものがいっぱいできます。
そこで、今回は宣言した項目の中から必要なものだけ抽出して返せるような仕組みを考えて見たいと思います。

selializer.py
class AnimeSelializer(serializers.ModelSerializer):

    def __init__(self, *args, **kwargs):
        super(AnimeSelializer, self).__init__(*args, **kwargs)
        try:
      context = kwargs.get('context')
            fields = context.get('fields', None)
            if fields:
                allowed = set(fields)
                existing = set(self.fields.keys())
                for field_name in existing - allowed:
                    self.fields.pop(field_name)
        except AttributeError:
            pass
    class Meta:
        model = Anime
        fields = ('id', 'title', 'start_date', 'end_date', 'director', 'is_harem', 'is_robot', 'thumbnail',)

例えば欲しい項目だけquerystringでこんな風に渡してきたとして

xxx.com/anime/v1/anime?field=id&field=title

view.py
class AnimetListView(APIView):

    def get(self, request):
        fields = request.GET.getlist("field") 
        animes = Anime.objects.all().order_by('-id')
        return Response(AnimeSelializer(animes, many=True, context=dict(fields=fields))

親の項目名を返したい

例えば、UserModelと一対一のprofileModelがあるとします。
当然usernameやemailなどuserに持っている項目はそれをそのまま返したいですよね。
こういう書き方もできますが、正直get_XXはあんまり書きたくないですよね

いまいちな書き方

selializer.py
class ProfileSerializer(serializers.ModelSerializer):
    name = serializers.SerializerMethodField()
    email = serializers.SerializerMethodField()

    class Meta:
        model = Anime
        fields = ('id', 'name', 'email', 'birthday', )

    def get_name(self, obj):
        return obj.user.username

    def get_email(self, obj):
        return obj.user.email

さっきよりはすっきりした書き方

selializer.py
class ProfileSerializer(serializers.ModelSerializer):
    name = serializers.CharField(source='user.username')
    email = serializers.EmailField(source='user.email')

    class Meta:
        model = Anime
        fields = ('id', 'name', 'email', 'birthday', )

すっきり書けますね(自分は最初user_usernameのように書いていて、上手くいかないなーと悩んでました
個人的にはfieldsにuser
_username と書ければ一番良いのですが、そういう書き方はちょっとわからなかったです。

methodを制限したい

なんらかの事情でpostやputをされたくないapiはあると思います。
その際はこんな感じで制限を掛けます。
継承基を制限すれば大丈夫そうです。
悪意のある人間がいきなりdeleteメソッドを送ってきてもこれで大丈夫ですね!

view.py
class AnimeViewSet(generics.ListAPIView, generics.RetrieveAPIView):
    queryset = Anime.objects.all()
    serializer_class = AnimeSerializer

参考)
http://www.django-rest-framework.org/api-guide/generic-views/#concrete-view-classes

処理を追いかけたい!

DRFは記述量が少なくて済む代わりに裏側で落ちたりすると原因がよく分からないことがあります。
そんな時の追いかけ方です。
とりあえず裏側で走ってしまってよく分からない部分を明示的に呼ぶようにして、そこからデバック実行します。
まぁ、、中まで突っ込んでいくことは滅多にありませんが。

view.py
    def list(self, request, *args, **kwargs):
        return super().list(request, *args, **kwargs)

    @transaction.atomic()
    def retrieve(self, request, *args, **kwargs):
        return super().retrieve(request, *args, **kwargs)

    @transaction.atomic
    def create(self, request, *args, **kwargs):
        return super().create(request, *args, **kwargs)

    @transaction.atomic
    def update(self, request, *args, **kwargs):
        return super().update(request, *args, **kwargs)

他にも業務上の小ネタ(特にnest関係)は結構ありますので、気がついたら更新していこうと思います