Search by

    Django REST Frameworkのチュートリアル(Tutorial 4 authentication and permissions)をやってみた

    Django REST Frameworkの公式チュートリアル(4-authentication-and-permissions)をやってみた時の不明点、気付きをまとめておく。

    概要

    APIに認証をつけようという話。

    ここでやりたいのは

    • Code snippets are always associated with a creator.
    • Only authenticated users may create snippets.
    • Only the creator of a snippet may update or delete it.
    • Unauthenticated requests should have full read-only access.
    • スニペットは常に作成者に関連づける。
    • 認証されたユーザーのみがスニペットを作れる。
    • スニペットを作成した人のみが更新や削除の作業ができる。
    • 認証されていないユーザーは読み取り専用のアクセスのみ

    だれでも勝手にいろんなこと出来たらダメだよね、ってことですね。

    スニペットモデルにownerの情報を関連づける

    スニペットにownerのリレーションを張る。 django本体の機能と大きな差はないように感じる。

    (私的な意見ですが、ここでシンタックスハイライトを保存するためのコード解説も付け加えているけど 若干脇道にそれている感も否めない。)

    リレーションを張ったら、一度DBを削除して、マイグレーション、ユーザー作成する。

    ユーザーモデルにエンドポイントを加える

    ユーザのリストを作成した時に、関連したスニペットを逆引きするように若干の仕込みがしてある。

    Because 'snippets' is a reverse relationship on the User model, it will not be included by default when using the ModelSerializer class, so we needed to add an explicit field for it.

    リバースとは、リレーションの参照先から参照元を探すこと。関連づけられたオブジェクトをリストに含めるには明示的に宣言する必要がある。

    コードはこんな感じにするようです。

    serializer.py
    from django.contrib.auth.models import User
    
    class UserSerializer(serializers.ModelSerializer):
        snippets = serializers.PrimaryKeyRelatedField(many=True, queryset=Snippet.objects.all())
    
        class Meta:
            model = User
            fields = ['id', 'username', 'snippets']

    primarykeyじゃなくて他のキーにも出来るのかもしれませんね。

    スニペット作成時にユーザー情報を保管する

    ここまでのコードでは、スニペットがPOSTされた時に、ユーザーの情報を保存する手段がない。 ユーザーの情報はシリアライズされて送られてくるのではなく、リクエストのプロパティーとして送られてくる。 ので、それをどの様に扱うのか解説してくれている。

    .perform_create()をオーバーライドする。

    that allows us to modify how the instance save is managed, and handle any information that is implicit in the incoming request or requested URL.

    このメソッドはインスタンスの保存の仕方や、リクエストに暗示されている情報、リクエストされたURLをどのように扱うかを定義している。

    こんな感じにする。腹落ちしない。

    views.py
    def perform_create(self, serializer):
        serializer.save(owner=self.request.user)

    スニペットシリアライザーにユーザー情報を追加する

    スニペットの情報を取得する時に、ユーザー情報も取得できるようにする。

    serializer.py
    owner = serializers.ReadOnlyField(source='owner.username')

    source引数は「なんの属性をここにポピュレートするか」を決めている。 ドット記法を使って、djangoのテンプレート言語と同じ様な方法で属性にアクセスする。

    ReadOnlyFieldは型なしのフールド。(dbのモデルを考える時に使うフィールド類とは少し毛色が違った考え方ですね) シリアライズする時に使う。デシリアライズする時には使わない。(読み取り専用だし当たり前か)

    認証情報の付与

    他のフレームワークはよくわからないけど djangoはView上での認証のコントロールが便利だと思う。 今回も必要なモジュールをインポートして、クラスに追加するだけでいい。

    views.py
    from rest_framework import permissions
    
    class SnippetList(generics.ListCreateAPIView):
        queryset = Snippet.objects.all()
        serializer_class = SnippetSerializer
        permission_classes = [permissions.IsAuthenticatedOrReadOnly]  

    Browsable APIにログイン機能をつける

    urls.pyでrest_framework.urlsをicludeするとログイン機能が追加される。 これでブラウザから、ログインが出来るようになる。 ブラウザでこの辺りもコントロールできるので、apiの挙動をテストしやすくなる。

    スニペットの削除、更新の権限を調整する。

    前段で下記Viewが作られている。

    views.py
    class SnippetDetail(generics.RetrieveUpdateDestroyAPIView):
        queryset = Snippet.objects.all()
        serializer_class = SnippetSerializer
        permission_classes = [permissions.IsAuthenticatedOrReadOnly] 

    これだと、認証ユーザーなら誰でもスニッペットの更新、削除が出来る状態になってしまう。 これは下記の当初要件が満たせない。

    • スニペットを作成した人のみが更新や削除の作業ができる

    そこで、認証をカスタマイズして自作する。

    permissions.py
    from rest_framework import permissions
    
    class IsOwnerOrReadOnly(permissions.BasePermission):
        """
        Custom permission to only allow owners of an object to edit it.
        """
    
        def has_object_permission(self, request, view, obj):
            # Read permissions are allowed to any request,
            # so we'll always allow GET, HEAD or OPTIONS requests.
            if request.method in permissions.SAFE_METHODS:
                return True
    
            # Write permissions are only allowed to the owner of the snippet.
            return obj.owner == request.user
    

    そして、Viewを更新。

    views.py
    class SnippetDetail(generics.RetrieveUpdateDestroyAPIView):
        queryset = Snippet.objects.all()
        serializer_class = SnippetSerializer
        permission_classes = [permissions.IsAuthenticatedOrReadOnly,
                          IsOwnerOrReadOnly]

    この部分よくわからない。 クラスで定義して、それをリストにして、認証情報を増やしている。 内部ではどんな動きになるのか。 クラスがインスタンス化した時に、has_object_permissionが呼ばれて、ユーザー情報判定するんだろうけど、IsOwnerOrReadOnlyクラスはいつインスタンス化されるんだろう。

    デフォルトの認証仕様

    ユーザーの認証情報をどの様に扱うか、認証仕様もフレームワーク上で指定できる模様。 デフォルトではSessionAuthentication, BasicAuthenticationとなっている。 認証仕様は、authetication classesで設定する。

    ブラウザでアクセスする時は、セッション情報が利用できるが、 プログラムでアクセスする時には、明示的にクレデンシャルを付与しないといけない。

    こんな感じ

    http -a admin:password123 POST http://127.0.0.1:8000/snippets/ code="print(789)"

    これまでの整理

    公式ページにはないが、ここでこれまでのチュートリアルで見てきたことを一旦整理してみる。

    • エンドポイントからデータを返す基本的な流れはdjangoの本体と同じ
    • シリアラーザーはdjango本体のformの様な感覚で利用できる。
    • modelシリアライザーでコードを簡素化出来る。
    • クラスベースViewの考え方も本家とほぼ同じ。
    • genericsクラスも用意されているので、これでコードを簡素化できる
      • ここで既存ブジェクト情報の必要有無に応じて[ List, Create ]と[ Retrieve, Update, Destroy] で2つのクラスを作る
    • リレーションも本家と類似、ユーザー情報などはシリアライズされず、Requestオブジェクトとして通信される。
    • ログインはurlで設定できる。
    • 認証情報もクラスとして用意されている。カスタマイズも可能。
    前の記事
    次の記事

    参考