LoginSignup
2
3

More than 3 years have passed since last update.

【Django】 ModelSerializerにAPIで認証したユーザーインスタンスを渡す

Posted at

若干詰まったのでメモ。

環境

  • Python 3.7.4
  • Django 3.0.3
  • django-rest-framework 3.11.0

ModelSerializerについて

django-rest-framework(以降 DRF)では、ModelSerializerというオブジェクトを介して、Json - Model間の変換を行う。
APIで受け取った値を編集して登録したり、テーブルに存在しないレコードを返したり、データの加工を行ってJson化する場合、Serializerにメソッドを追加する場合が多い。

またDRFではトークン認証を行った場合、requestオブジェクトにuserインスタンスが格納される。
Serializerにuserインスタンスを渡すと、リレーション先の値を自動で入れられるので、利用シーンは多い。

model

前提となるVisitorモデルは以下。

import uuid
from django.db import models
from api.models import AppUser
from web.models import AdminUser


class Visitor(models.Model):
    class Meta:
        db_table = "visitor"
        verbose_name_plural = '訪問者'

    uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    parent_user = models.ForeignKey(AdminUser,
                                    on_delete=models.CASCADE,
                                    related_name='visitor')
    name = models.CharField(max_length=50)
    email = models.EmailField(max_length=255, null=True, blank=True)
    company = models.CharField(max_length=50, blank=True, null=True)
    memo = models.TextField(blank=True, null=True, max_length=300)
    visit_count = models.PositiveIntegerField(default=0)
    created_by = models.ForeignKey(AppUser,
                                   on_delete=models.SET_NULL,
                                   null=True, blank=True,
                                   related_name='visitor')
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    def __str__(self):
        return self.name

serializer

serializerはモデルと結び付いたfieldsを生成する。値を任意に変更するには、SerializerMethodField()でオーバーライドする。
SerializerMethodField()で定義したfieldはget_<field名>というメソッドを呼び出し、その返り値を格納する。

get_<field名>のメソッドは引数にinstanceを持つ。
そのため、API認証を行った際ここに渡されると思っていたのだが、ここはCreate時に作成されたインスタンスそのものが渡されるようだ。
(これを利用すれば、リレーション先のデータを自動作成も出来る。)

from rest_framework.serializers import ModelSerializer, SerializerMethodField

class VisitorCreateSerializer(ModelSerializer):
    created_by = SerializerMethodField()
    parent_user = SerializerMethodField()

    class Meta:
        model = Visitor
        read_only_fields = (
            'pk',
            'created_at',
            'created_by',
        )
        fields = (
            'pk',
            'parent_user',
            'created_at',
            'name',
            'email',
            'company',
            'memo',
            'updated_at',
        )

    def get_created_by(self, instance):
        """送信したユーザーがcreated_byに格納される"""
        return str(instance.pk)

    def get_parent_user(self, instance):
        """parent_userを自動的に格納する"""
        return str(instance.parent_user.pk)

ViewSet

Viewにおいて、Serializerをインスタンス化する部分があるので、ここでinstanceにuserを渡せばOK.
今回はcreateだけserializerを分けたかったのでcreateメソッドをオーバーライドして、Serializerを指定している。

# DRF
from rest_framework import status, viewsets
from rest_framework.permissions import IsAuthenticated
from .serializers import VisitorSerializer, VisitorCreateSerializer
from .models import Visitor
from utils.auth.authentication import APIAuthentication
from rest_framework.response import Response


class VisitorViewSet(viewsets.ModelViewSet):
    serializer_class = VisitorSerializer # create以外に使用するserializer
    queryset = Visitor.objects.all()

    authentication_classes = (APIAuthentication,)
    permission_classes = (IsAuthenticated,)

    def get_queryset(self):
        # API認証した場合,userインスタンスはrequest.userに格納される。
        admin = self.request.user.parent_user
        qs = Visitor.objects.filter(parent_user=admin)
        return qs

    # createメソッドをオーバーライド
    def create(self, request, *args, **kwargs):
        # Serializerにrequest.userを渡すと、get_<field名>メソッドにinstanceが渡る
        serializer = VisitorCreateSerializer(instance=request.user, data=request.data)
        # ここから下はそのまま
        serializer.is_valid(raise_exception=True)
        self.perform_create(serializer)
        headers = self.get_success_headers(serializer.data)
        return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
2
3
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
2
3