Django REST framework のチュートリアルやってみた③

前回の記事はこちら

ユーザ認証

前回までの記事の内容ではblogモデル1つ1つとユーザーは結びついておらず誰でも自由に新規作成、編集、削除等の操作ができてしまいました。今回はそれを修正して、適切なユーザにのみ行えるようにしていきます。具体的には、、、

  • それぞれの記事は作成者が誰なのかを保持する
  • 認証されたユーザのみ新規作成を行える
  • 記事の作成者のみが編集と削除を行える
  • 認証されていない場合はデータの閲覧のみとなる

この4つの制限を加えていきます。

models

まずmodels.pyに以下のフィールドを加えます。

owner = models.ForeignKey('auth.User', related_name='blog', on_delete=models.CASCADE)

それぞれの記事を誰が書いたのかを保持するフィールドです。ForeignKeyとすることでユーザーモデルと紐付けることができます。

データベース削除

一度データベースを削除して作り直します。以下の4つのコマンドを実行してください。

rm -f db.sqlite3
rm -r blog/migrations
python manage.py makemigrations blog
python manage.py migrate

また以下のコマンドでスーパーユーザをいくつか作成しておきます。(僕は3つ作っておきました。)

python manage.py createsuperuser

serializers.py

1つのインポート文と、serializerクラスを追加します。

from django.contrib.auth.models import User

class UserSerializer(serializers.ModelSerializer):
    blog = serializers.PrimaryKeyRelatedField(many=True, queryset=Blog.objects.all())
    
    class Meta:
        model = User
        fields = ['id', 'username', 'blog']

blogにはユーザと関連付けられているBlogモデルのデータが追加されます。

以下はDjango REST framework 公式のPrimaryKeyRelatedFieldのページです。

Serializer relations - Django REST framework
Django, API, REST, Serializer relations

views.py

viewを書き加えます。

from django.contrib.auth.models import User
from blog.serializers import UserSerializer

class UserList(generics.ListAPIView):
    queryset = User.objects.all()
    serializer_class = UserSerializer

class UserDetail(generics.RetrieveAPIView):
    queryset = User.objects.all()
    serializer_class = UserSerializer

ユーザの一覧を表示するためのクラス(UserList)とそれぞれのユーザの詳細を確認するクラス(UserDetail)です。

blog/urls.py

from .views import BlogList, BlogDetail, UserList, UserDetail

path('users/', UserList.as_view()),
path('users/<int:pk>/', UserDetail.as_view()),

viewsからUserListとUserDetailのインポートをしてpathを追加します。

perform_createのオーバーライド

現時点ではデータ作成時にユーザとブログ投稿のデータが関連付けできないためperform_createメソッドをオーバーライドすることで解決します。views.pyのBlogListクラスに書きたします。

class BlogList(generics.ListCreateAPIView):
    queryset = Blog.objects.all()
    serializer_class = BlogSerializer
    
    def perform_create(self, serializer): # 追加
        serializer.save(owner=self.request.user) # 追加

これでownerにはリクエストしてきたユーザがセットされることになります。

BlogSerializerのアップデート

blog/serializers.pyの中のBlogSerializerに少し書きたします。

class BlogSerializer(serializers.ModelSerializer):
    owner = serializers.ReadOnlyField(source='owner.username') # 追加

    class Meta:
        model = Blog
        fields = ['id', 'title', 'content', 'owner'] # owner追加

sourceにowner.usernameとすることでユーザモデルの中のusernameを取ってくる事ができます。

制限をかける

現時点で記事を作成したユーザが誰なのかといった情報と逆にユーザはどのブログ記事を書いたかという情報を関連させる事ができました。ここからユーザごとに適切な権限を与えるための記述をしていきます。まずはviews.pyの変更です。

from .models import Blog
from .serializers import BlogSerializer
from rest_framework import generics
from django.contrib.auth.models import User
from blog.serializers import UserSerializer
from rest_framework import permissions # 追加

class BlogList(generics.ListCreateAPIView):
    queryset = Blog.objects.all()
    serializer_class = BlogSerializer
    permission_classes = [permissions.IsAuthenticatedOrReadOnly] # 追加
    
    def perform_create(self, serializer):
        serializer.save(owner=self.request.user)



class BlogDetail(generics.RetrieveUpdateDestroyAPIView):
    queryset = Blog.objects.all()
    serializer_class = BlogSerializer
    permission_classes = [permissions.IsAuthenticatedOrReadOnly] # 追加

このIsAuthenticatedOrReadOnlyをpermission_classesに追加する事で認証されたユーザのリクエストは読み書きが可能になり、認証されていないリクエストは読み取りのみ可能になります。

また記事の編集や削除はその記事の作成者のみが行えるようにしたいので、BlogDetailクラスにはさらに制限が必要です。blogフォルダ内にpermissions.pyファイルを新しく作り、以下の内容を書きます。

from rest_framework import permissions


class IsOwnerOrReadOnly(permissions.BasePermission):

    def has_object_permission(self, request, view, obj):
        if request.method in permissions.SAFE_METHODS:
            return True

      
        return obj.owner == request.user

このクラスではリクエストがSAFE_METHODSつまり、GET, HEAD もしくは OPTIONSだった場合はアクセスが許可されます。なので記事の作者ではない場合でも、記事の詳細は閲覧する事ができます(詳細を見るときのリクエストはGET method なので)。しかしリクエストがPUTやDELETEの場合に限ってはリクエストしてきたユーザが記事のowner出ない場合は無効となります。このクラスをviewsのBlogDetailクラスに適応します。permission_classesを以下のように変更してください。

permission_classes = [permissions.IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly]

またIsOwnerOrReadOnlyクラスのインポートもします。

from .permissions import IsOwnerOrReadOnly

blog/urls.py

最後に少しだけblog/urls.pyを書き換えます。includeのインポートとpathを1つ追加します。

from django.conf.urls import include
path('api-auth/', include('rest_framework.urls')),

これを追加する事で、ブラウザ上でのログインやログアウトが可能になり、先ほど追加したアクセス権限などが適応されているのかブラウザ上で確認する事ができます。

動作確認

runserverしてブラウザで動作確認してみます。ログインなしでhttp://localhost:8000/blog/にアクセスすると、、、

今現在何も記事を登録してないため何も表示されません。さらに新しく記事を追加することもできません。

次に先ほど作成したスーパーユーザでログインして同じくhttp://localhost:8000/blog/にアクセスしてみます。すると、、、

記事登録用のフォームが現れました。いくつか記事を追加してみます。

それぞれの記事が追加したときのユーザであるadminを保持している事がわかります。一度ログアウトして他のユーザーでも記事を作成していきます。

別のユーザが作成した記事の詳細ページにアクセスすると閲覧する事はできますが、編集や削除はできなくなっているはずです。しかし、ログイン中のユーザと作成したユーザが同じ場合の詳細ページでは編集や削除が可能となっています。

またhttp://localhost:8000/users/にアクセスするとuserの一覧が確認でき、それぞれのユーザが作成した記事のidを確認する事ができます。

参考

公式チュートリアルを参考にして書きました。

4 - Authentication and permissions - Django REST framework
Django, API, REST, 4 - Authentication and permissions

コメント

タイトルとURLをコピーしました