前回はクラスビューベースで処理を記述していく方法について「DjangoRESTframework」の使い方を学んでいきました。
今回は、公式ドキュメントのチュートリアルに沿って「Tutorial 4: Authentication & Permissions」について解説していきたいと思います。
チュートリアルを簡単に日本語訳したものになりますので、英語に抵抗がない方は公式ドキュメントを参考にすることをお勧めします。
本記事の目的は「DjangoRESTframework」の入門編として読み物的な感じで読んでいただけますと幸いです。
ユーザー認証機能を実装する
今までは、誰でもスニペットを取得・作成・変更・削除ができましたが、実際にAPIを実装しようとするときはユーザー認証が必要ですよね。
なので今回は、ユーザー認証機能を実装する方法を解説していきます。
Modelに情報を追加する
ユーザー認証機能を実装するには、スニペットを誰が作成したのかの情報を追加していく必要がありますので追加していきましょう。
※ユーザーモデルはDjangoにデフォルトで実装されているユーザーモデルを使用します
snippeets/models.py
ファイル内のSnippets
モデルの中に下記コードを追加していきます。
owner = models.ForeignKey('auth.User', related_name='snippets', on_delete=models.CASCADE)
highlighted = models.TextField()
また、スニペットを保存した時に強調されたHTMLを作成したいので「pygments」というライブラリを使って、保存時に強調されるようにしたいと思います。
こちらもsnippeets/models.py
ファイル内のSnippets
モデルの中に下記コードを追加していきます。
from pygments.lexers import get_lexer_by_name
from pygments.formatters.html import HtmlFormatter
from pygments import highlight
def save(self, *args, **kwargs):
"""
Use the `pygments` library to create a highlighted HTML
representation of the code snippet.
"""
lexer = get_lexer_by_name(self.language)
linenos = 'table' if self.linenos else False
options = {'title': self.title} if self.title else {}
formatter = HtmlFormatter(style=self.style, linenos=linenos,
full=True, **options)
self.highlighted = highlight(self.code, lexer, formatter)
super(Snippet, self).save(*args, **kwargs)
実際にすべて書き換えたmodels.pyはこんな感じになります。
from django.db import models
from pygments import highlight
from pygments.formatters.html import HtmlFormatter
from pygments.lexers import get_all_lexers, get_lexer_by_name
from pygments.styles import get_all_styles
LEXERS = [item for item in get_all_lexers() if item[1]]
LANGUAGE_CHOICES = sorted([(item[1][0], item[0]) for item in LEXERS])
STYLE_CHOICES = sorted([(item, item) for item in get_all_styles()])
class Snippet(models.Model):
created = models.DateTimeField(auto_now_add=True)
title = models.CharField(max_length=100, blank=True, default='')
code = models.TextField()
linenos = models.BooleanField(default=False)
language = models.CharField(choices=LANGUAGE_CHOICES, default='python', max_length=100)
style = models.CharField(choices=STYLE_CHOICES, default='friendly', max_length=100)
owner = models.ForeignKey('auth.User', related_name='snippets', on_delete=models.CASCADE)
highlighted = models.TextField()
def save(self, *args, **kwargs):
"""
「pygments」ライブラリを使ってHTMLを使って強調表示されたHTMLを作成します
"""
lexer = get_lexer_by_name(self.language)
linenos = 'table' if self.linenos else False
options = {'title': self.title} if self.title else {}
formatter = HtmlFormatter(style=self.style, linenos=linenos,
full=True, **options)
self.highlighted = highlight(self.code, lexer, formatter)
super(Snippet, self).save(*args, **kwargs)
class Meta:
ordering = ['created']
情報を追加したのでmaigrateしていきたいのですが、このままではmakemigrationsが通りません。
(すでにレコードが存在するので、そのレコードに対して今回追加したカラムをどう扱うか決まっていないため)
通常は、DBを移行したり追加したカラムにデフォルト値を設定する、もしくはNullを許容させるのですが、今回はDBをまるっと削除してしまいましょう。
また、makemigrationsし直したいので、今までのmaigrationsも削除しておきます。
rm -f db.sqlite3
rm -r snippets/migrations
python manage.py makemigrations snippets
python manage.py migrate
DBを削除したので管理者ユーザーを作り直しておきます。
ここでは仮に、IDをadmin
PWをpassword123
しておきます。
python manage.py createsuperuser
これで下準備はできましたので、実際に認証機能を実装していきましょう。
ユーザーモデルのエンドポイントを追加する
ユーザーモデルについてもAPIで操作できるようにエンドポイントを追加していきます。
まずは、snippets/serializers.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']
次に、snippets/views.py
にもクラスベースのビューを追加していきます。
from snippets.serializers import UserSerializer
from django.contrib.auth.models import User
class UserList(generics.ListAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
class UserDetail(generics.RetrieveAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
最後にsnippets/urls.py
にエンドポイントを追加します。
path('users/', views.UserList.as_view()),
path('users/<int:pk>/', views.UserDetail.as_view()),
これでエンドポイントを追加することができました。
スニペットとユーザーの紐づけをする
.perform_create()
関数をオーバーライドしてスニペットを保存する時に誰が作成したのかの情報を保存していきます。snippets/views.py
内にあるSnippetList
にオーバーライドしたものを追加していきます。
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
シリアライザーの更新
スニペットを作成したユーザーを紐づけることができたので、シリアライザーも更新しておきます。snippets/serializers.py
内のSnippetSerializer
に下記コードを追加していきます。
owner = serializers.ReadOnlyField(source='owner.username')
また、Meta
クラス内のfields
にもowner
を追加してください。
最終的にはこんな感じになります。
from django.contrib.auth.models import User
from rest_framework import serializers
from snippets.models import Snippet
class SnippetSerializer(serializers.ModelSerializer):
owner = serializers.ReadOnlyField(source='owner.username')
class Meta:
model = Snippet
fields = ['id', 'owner', 'title', 'code', 'linenos', 'language', 'style']
class UserSerializer(serializers.ModelSerializer):
snippets = serializers.PrimaryKeyRelatedField(many=True, queryset=Snippet.objects.all())
class Meta:
model = User
fields = ['id', 'username', 'snippets']
Viewsに認証機能を実装する
認証されたユーザーのみ作成、更新、削除できるようにしていきます。REST framework
が用意しているIsAuthenticatedOrReadOnly
を利用して実装していきます。
snippets/views.py
内でモジュールをインポートします。
from rest_framework import permissions
モジュールをインポートできましたら、SnippetList
クラスとSnippetDetail
クラスに下記コードを追加します。
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
ブラウザからログインできるようにする
現状、ブラウザからではログインできないのでスニペットを作成することができなくなっています。
なので、ブラウザからもログインできるようにしておきましょう。tutorial/urls.py
ファイルに以下を追加していきます。
urlpatterns += [
path('api-auth/', include('rest_framework.urls')),
]
最終的にはこんな感じになります。
from django.urls import path, include
urlpatterns = [
path('', include('snippets.urls')),
path('api-auth/', include('rest_framework.urls')),
]
オブジェクトの認証機能の実装
まず、認証権限について記述していきます。snippets
フォルダ配下にpermissions.py
を作成します。
中身はこんな感じで記述します。
from rest_framework import permissions
class IsOwnerOrReadOnly(permissions.BasePermission):
"""
オーナーの場合のみ変更と削除を許可する
"""
def has_object_permission(self, request, view, obj):
# GET,HEAD,OPTIONSメソッドの時は問題ない
if request.method in permissions.SAFE_METHODS:
return True
return obj.owner == request.user
GETやHEAD、OPTIONSメソッド等は読み込みだけですので、それらのメソッドでリクエストされた場合はTrue
を返します。
それ以外は、オブジェクトのオーナーとリクエストしたユーザーが同じであればTrue
を返します。
先程snippets/views.py
内のSnippetDetail
クラスに追加した箇所を変更します。
permission_classes = [permissions.IsAuthenticatedOrReadOnly,
IsOwnerOrReadOnly]
また、IsOwnerOrReadOnly
モジュールをインポートしなければ動きませんので、インポートしておきましょう。
from snippets.permissions import IsOwnerOrReadOnly
snippets/views.py
は最終的にはこんな感じになります。
from django.contrib.auth.models import User
from django.http import Http404
from rest_framework import generics, permissions, status
from rest_framework.response import Response
from rest_framework.views import APIView
from snippets.models import Snippet
from snippets.permissions import IsOwnerOrReadOnly
from snippets.serializers import SnippetSerializer, UserSerializer
class SnippetList(generics.ListCreateAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
class SnippetDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
permission_classes = [permissions.IsAuthenticatedOrReadOnly,
IsOwnerOrReadOnly]
class UserList(generics.ListAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
class UserDetail(generics.RetrieveAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
これで、ユーザー認証機能を実装できました。
APIを実際に叩いてみる
これで実装できましたので、最後に実際にAPIを叩いてみましょう。
認証方法はデフォルトのSessionAuthentication
とBasicAuthentication
になっておりますので、ブラウザからログインした場合はセッションから認証して、APIからリクエストする場合はBasic認証を利用してリクエストを出します。
まずは、認証情報なしでリクエストしてみます。
$ http POST http://127.0.0.1:8000/snippets/ code="print(123)"
中略
{
"detail": "認証情報が含まれていません。"
}
認証情報が無いのでスニペットを作成することができませんでしたね。
それでは、次に認証情報を追加してリクエストしてみたいと思います。
$ http -a admin:password123 POST http://127.0.0.1:8000/snippets/ code="print(789)"
中略
{
"code": "print(789)",
"id": 1,
"language": "python",
"linenos": false,
"owner": "admin",
"style": "friendly",
"title": ""
}
-a admin:password123
の部分は、createsuperuser
コマンドで管理者ユーザーを作成した際のIDとPWを指定します。
しっかり認証機能が実装できましたね。
今回はここまでにしたいと思います。
まとめ
今回はユーザー認証を実装する方法を解説していきました。
次回はハイパーリンクの関係性をAPIで実装する方法を解説していきます。
不明点等ございましたら、コメントかTwitterのDMでご質問ください。
コメント