こんにちは。KOUKIです。
前回の続きです。本日は、Comment APIを作成します。
<目次>
記事まとめ
Comment Model作成とテスト
ブログのコメントを作成するため、Comment Modelを作成します。
まずは、テストコードを作成します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
# app/blog/tests/test_models.py from django.test import TestCase from django.contrib.auth import get_user_model from blog.models import Tag, Comment # new def sample_user(email='test@selfnote.work', password='SELF NOTE'): return get_user_model().objects.create_user(email, password) class ModelTests(TestCase): def test_tag_str(self): ... # new def test_comment_str(self): """Check if the comment string representation""" c = Comment.objects.create( user=sample_user(), comment='This is a sample comment.' ) self.assertEqual(str(c), c.comment) |
Commentを作成したとき、コメント文字が返されるか確認するテストです。
テストを実行しましょう。
1 2 3 |
make test app=blog E ImportError: cannot import name 'Comment' from 'blog.models' (/app/blog/models.py) |
Comment Modelがないため、エラーになりましたね。
Comment Modelを作成しましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
# app/blog/models.py from django.db import models from django.conf import settings def set_user(): return models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.CASCADE, ) class Tag(models.Model): name = models.CharField(max_length=255) user = set_user() def __str__(self): return self.name class Comment(models.Model): comment = models.CharField(max_length=500) user = set_user() def __str__(self): return self.comment |
マイグレーションを実行しましょう。
1 |
make migrate |
テストを実行します。
1 2 3 |
make test app=blog ===== 7 passed in 2.10s ===== |
テストがパスしましたね。
管理画面からCommentを追加できるようにadmin.pyに以下を追加しましょう。
1 2 3 4 5 6 7 |
# app/blog/admin.py from django.contrib import admin from .models import Tag, Comment admin.site.register(Tag) admin.site.register(Comment) |
Comment APIテスト
次は、Comment APIテストを作成します。
1 |
touch app/blog/tests/test_comments_api.py |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
# app/blog/tests/test_comments_api.py from django.contrib.auth import get_user_model from django.urls import reverse from django.test import TestCase from rest_framework import status from rest_framework.test import APIClient from blog.models import Comment from blog.serializers import CommentSerializer COMMENTS_URL = reverse('blog:comment-list') class PublicCommentAPITests(TestCase): def setUp(self): self.client = APIClient() # 1 def test_login_required(self): """Check if login is required to access the endpoint""" res = self.client.get(COMMENTS_URL) self.assertEqual(res.status_code, status.HTTP_401_UNAUTHORIZED) class PrivateCommentAPITests(TestCase): def setUp(self): self.client = APIClient() self.user = get_user_model().objects.create_user( 'test@selfnote.work', 'SELF NOTE' ) self.client.force_authenticate(self.user) # 2 def test_retrieve_comment_list(self): """Check if retrieving a list of comments""" Comment.objects.create(user=self.user, comment='This is a sample comment1') Comment.objects.create(user=self.user, comment='This is a sample comment2') res = self.client.get(COMMENTS_URL) comments = Comment.objects.all().order_by('-comment') serializer = CommentSerializer(comments, many=True) self.assertEqual(res.status_code, status.HTTP_200_OK) self.assertEqual(res.data, serializer.data) # 3 def test_comments_limited_to_user(self): """Check if comments for the authenticated user are returned""" user2 = get_user_model().objects.create_user( 'other@londonappdev.com', 'SELF NOTE', ) Comment.objects.create(user=user2, comment='User2 comment') c = Comment.objects.create(user=self.user, comment='User Comment') res = self.client.get(COMMENTS_URL) self.assertEqual(res.status_code, status.HTTP_200_OK) self.assertEqual(len(res.data), 1) self.assertEqual(res.data[0]['comment'], c.comment) |
テストの意味については、次の通りです。
- #1 -> 未ログインユーザーのアクセスに対して、401エラーを発生させる
- #2 -> ログイン済みユーザーのアクセスに対して、Commentリストを返す
- #3 -> ログイン済みのユーザーかつ自分のコメントしか取得できないか確認する
テストを実行します。
1 2 3 |
make test app=blog E ImportError: cannot import name 'CommentSerializer' from 'blog.serializers' (/app/blog/serializers.py) |
CommentSerializerが存在しないため、エラーになりましたね。
CommentSerializerを作成しましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
# app/blog/serializers.py from rest_framework import serializers from blog.models import Tag, Comment # new class TagSerializer(serializers.ModelSerializer): ... class CommentSerializer(serializers.ModelSerializer): class Meta: model = Comment fields = ('id', 'comment') read_only_fields = ('id',) |
テストを実行します。
1 2 3 |
make test blog=test E django.urls.exceptions.NoReverseMatch: Reverse for 'comment-list' not found. 'comment-list' is not a valid view function or pattern name. |
「COMMENTS_URL = reverse(‘blog:comment-list’)」にて、エラーがでましたね。urls.pyにパスを登録していないからです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
# app/blog/urls.py from django.urls import path, include from rest_framework.routers import DefaultRouter from blog import views # https://www.django-rest-framework.org/api-guide/routers/#api-guide router = DefaultRouter() router.register('tags', views.TagViewSet) router.register('comments', views.CommentViewSet) # new app_name = 'blog' urlpatterns = [ path('', include(router.urls)), ] |
テストを実行します。
1 2 3 |
make test app=blog E AttributeError: module 'blog.views' has no attribute 'CommentViewSet' |
CommentViewSetの設定がされていませんので、エラーになりました。
blogアプリのviews.pyに追加しましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
# app/blog/views.py from rest_framework import viewsets, mixins from rest_framework.authentication import TokenAuthentication from rest_framework.permissions import IsAuthenticated from blog.models import Tag, Comment # new from .serializers import TagSerializer, CommentSerializer # new class TagViewSet(viewsets.GenericViewSet, mixins.ListModelMixin, mixins.CreateModelMixin): ... class CommentViewSet(viewsets.GenericViewSet, mixins.ListModelMixin): authentication_classes = (TokenAuthentication,) permission_classes = (IsAuthenticated,) queryset = Comment.objects.all() serializer_class = CommentSerializer def get_queryset(self): return self.queryset.filter(user=self.request.user).order_by('-comment') |
テストを実行します。
1 2 3 |
make test app=blog == 10 passed in 2.45s == |
Comment作成機能
次は、Comment作成機能を実装します。
まずは、テストコードからです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
# app/blog/tests/test_comments_api.py from django.contrib.auth import get_user_model from django.urls import reverse from django.test import TestCase from rest_framework import status from rest_framework.test import APIClient from blog.models import Comment from blog.serializers import CommentSerializer COMMENTS_URL = reverse('blog:comment-list') class PublicCommentAPITests(TestCase): ... class PrivateCommentAPITests(TestCase): ... self.assertEqual(res.data[0]['comment'], c.comment) def test_create_comment_successful(self): """Check if creating a new comment""" payload = {'comment': 'This is a sample comment'} self.client.post(COMMENTS_URL, payload) exists = Comment.objects.filter( user=self.user, comment=payload['comment'], ).exists() self.assertTrue(exists) def test_create_comment_invalid(self): "Check if creating comment fail with invalid parameter" payload = {'comment': ''} res = self.client.post(COMMENTS_URL, payload) self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST) |
有効・無効なパラメータを渡したときのComment Model作成テストです。
テストを実行します。
1 2 3 4 5 6 7 8 9 |
make test app=blog _ PrivateCommentAPITests.test_create_comment_invalid _ > self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST) E AssertionError: 405 != 400 _ PrivateCommentAPITests.test_create_comment_successful _ > self.assertTrue(exists) E AssertionError: False is not true |
まだpostに対する処理を実装していないので、当然エラーになりますね。
blogアプリのCommentViewSetを修正しましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
# app/blog/views.py from rest_framework import viewsets, mixins from rest_framework.authentication import TokenAuthentication from rest_framework.permissions import IsAuthenticated from blog.models import Tag, Comment from .serializers import TagSerializer, CommentSerializer class TagViewSet(viewsets.GenericViewSet, mixins.ListModelMixin, mixins.CreateModelMixin): ... class CommentViewSet(viewsets.GenericViewSet, mixins.ListModelMixin, mixins.CreateModelMixin): # new authentication_classes = (TokenAuthentication,) permission_classes = (IsAuthenticated,) queryset = Comment.objects.all() serializer_class = CommentSerializer def get_queryset(self): return self.queryset.filter(user=self.request.user).order_by('-comment') # new def perform_create(self, serializer): serializer.save(user=self.request.user) |
テストを実行します。
1 2 3 |
make test app=blog === 12 passed in 2.64s == |
リファクタリング
少しviews.pyのコードが煩雑になってきたので、リファクタリングします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
# app/blog/views.py from rest_framework import viewsets, mixins from rest_framework.authentication import TokenAuthentication from rest_framework.permissions import IsAuthenticated from blog.models import Tag, Comment from .serializers import TagSerializer, CommentSerializer class BaseBlogAttrViewSet( viewsets.GenericViewSet, mixins.ListModelMixin, mixins.CreateModelMixin): authentication_classes = (TokenAuthentication,) permission_classes = (IsAuthenticated,) def perform_create(self, serializer): serializer.save(user=self.request.user) class TagViewSet(BaseBlogAttrViewSet): queryset = Tag.objects.all() serializer_class = TagSerializer def get_queryset(self): return self.queryset.filter(user=self.request.user).order_by('-name') class CommentViewSet(BaseBlogAttrViewSet): queryset = Comment.objects.all() serializer_class = CommentSerializer def get_queryset(self): return self.queryset.filter(user=self.request.user).order_by('-comment') |
テストを実行します。
1 2 3 |
make test app=blog == 12 passed in 2.70s == |
これで、Comment APIの完成です。
次回
次回は、Blog APIを作成していきましょう。
関連記事
基礎編はこちらです。
コメントを残す
コメントを投稿するにはログインしてください。