ここではコメントに投稿したユーザの情報,すなわちオーナ情報を追加します.まず,models.py を編集して,User
モデルとのリレーションシップを設定します.なお,リレーションシップの詳細はこちらを参照してください.
comments\models.py
from django.db import models
from django.contrib.auth.models import User
# Create your models here.
class Comment(models.Model):
title = models.CharField(max_length=200)
body = models.CharField(max_length=1000)
owner = models.ForeignKey(User, on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
ordering = ['-updated_at']
comments_comment テーブルに ower_id のフィールドを追加することになるので,データベースとマイグレーション関連のファイルをディレクトリごと一旦削除します.SQLite や Web サーバを別のプロンプト(ターミナル)で起動中であれば,あらかじめ終了ししてから,次のコマンドを実行してください.なお,すでに本番環境で運用中のデータベースにフィールドを追加する場合はリセットできないので,ここの手順を参照してください.
...\django_comment_api>del db.sqlite3 ⏎ ...\django_comment_api>rmdir /S comments\migrations ⏎ comments\migrations、よろしいですか (Y/N)? y ⏎ ...\django_comment_api>
続いてマイグレーションファイルの生成を行ったのち,マイグレーションファイルを実行してデータベースにテーブルを再生成します.
...\django_comment_api>python manage.py showmigrations ⏎ admin [ ] 0001_initial [ ] 0002_logentry_remove_auto_add [ ] 0003_logentry_add_action_flag_choices auth [ ] 0001_initial [ ] 0002_alter_permission_name_max_length [ ] 0003_alter_user_email_max_length [ ] 0004_alter_user_username_opts [ ] 0005_alter_user_last_login_null [ ] 0006_require_contenttypes_0002 [ ] 0007_alter_validators_add_error_messages [ ] 0008_alter_user_username_max_length [ ] 0009_alter_user_last_name_max_length [ ] 0010_alter_group_name_max_length [ ] 0011_update_proxy_permissions [ ] 0012_alter_user_first_name_max_length contenttypes [ ] 0001_initial [ ] 0002_remove_content_type_name sessions [ ] 0001_initial ...\django_comment_api>python manage.py makemigrations comments ⏎ Migrations for 'comments': comments\migrations\0001_initial.py - Create model Comment ...\django_comment_api>python manage.py showmigrations ⏎ admin [ ] 0001_initial [ ] 0002_logentry_remove_auto_add [ ] 0003_logentry_add_action_flag_choices auth [ ] 0001_initial [ ] 0002_alter_permission_name_max_length [ ] 0003_alter_user_email_max_length [ ] 0004_alter_user_username_opts [ ] 0005_alter_user_last_login_null [ ] 0006_require_contenttypes_0002 [ ] 0007_alter_validators_add_error_messages [ ] 0008_alter_user_username_max_length [ ] 0009_alter_user_last_name_max_length [ ] 0010_alter_group_name_max_length [ ] 0011_update_proxy_permissions [ ] 0012_alter_user_first_name_max_length comments [ ] 0001_initial contenttypes [ ] 0001_initial [ ] 0002_remove_content_type_name sessions [ ] 0001_initial ...\django_comment_api>python manage.py migrate ⏎ Operations to perform: Apply all migrations: admin, auth, comments, contenttypes, sessions Running migrations: Applying contenttypes.0001_initial... OK Applying auth.0001_initial... OK Applying admin.0001_initial... OK Applying admin.0002_logentry_remove_auto_add... OK Applying admin.0003_logentry_add_action_flag_choices... OK Applying contenttypes.0002_remove_content_type_name... OK Applying auth.0002_alter_permission_name_max_length... OK Applying auth.0003_alter_user_email_max_length... OK Applying auth.0004_alter_user_username_opts... OK Applying auth.0005_alter_user_last_login_null... OK Applying auth.0006_require_contenttypes_0002... OK Applying auth.0007_alter_validators_add_error_messages... OK Applying auth.0008_alter_user_username_max_length... OK Applying auth.0009_alter_user_last_name_max_length... OK Applying auth.0010_alter_group_name_max_length... OK Applying auth.0011_update_proxy_permissions... OK Applying auth.0012_alter_user_first_name_max_length... OK Applying comments.0001_initial... OK Applying sessions.0001_initial... OK ...\django_comment_api>python manage.py showmigrations ⏎ admin [X] 0001_initial [X] 0002_logentry_remove_auto_add [X] 0003_logentry_add_action_flag_choices auth [X] 0001_initial [X] 0002_alter_permission_name_max_length [X] 0003_alter_user_email_max_length [X] 0004_alter_user_username_opts [X] 0005_alter_user_last_login_null [X] 0006_require_contenttypes_0002 [X] 0007_alter_validators_add_error_messages [X] 0008_alter_user_username_max_length [X] 0009_alter_user_last_name_max_length [X] 0010_alter_group_name_max_length [X] 0011_update_proxy_permissions [X] 0012_alter_user_first_name_max_length comments [X] 0001_initial contenttypes [X] 0001_initial [X] 0002_remove_content_type_name sessions [X] 0001_initial ...\django_comment_api>
SQLite を操作して生成されたテーブルの定義を確認します.すると,auth_user テーブルの主キーを参照する外部キー owner_id が追加されたことがわかりました.
sqlite> .tables ⏎ auth_group comments_comment auth_group_permissions django_admin_log auth_permission django_content_type auth_user django_migrations auth_user_groups django_session auth_user_user_permissions sqlite> .schema comments_comment ⏎ CREATE TABLE IF NOT EXISTS "comments_comment" ( "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "title" varchar(200) NOT NULL, "body" varchar(1000) NOT NULL, "created_at" datetime NOT NULL, "updated_at" datetime NOT NULL, "owner_id" integer NOT NULL REFERENCES "auth_user" ("id") DEFERRABLE INITIALLY DEFERRED ); CREATE INDEX "comments_comment_owner_id_26de7a7f" ON "comments_comment" ("owner_id"); sqlite>
さらに JSON ファイルを生成して,登録するデータに owner_id の情報を追加します.
comments\fixtures\comments-data.json
[
{
"model": "comments.comment",
"fields": {
"title": "最初のコメント",
"body": "コメントの本文",
"owner_id": 1,
"created_at": "2023-11-23T11:01:00.000",
"updated_at": "2023-11-23T11:01:00.000"
}
},
{
"model": "comments.comment",
"fields": {
"title": "2個目のコメント",
"body": "コメントの本文2",
"owner_id": 2,
"created_at": "2023-11-23T11:02:00.000",
"updated_at": "2023-11-23T11:02:00.000"
}
},
{
"model": "comments.comment",
"fields": {
"title": "<3個目>のコメント",
"body": "<h1>コメントの本文3</h1>",
"owner_id": 1,
"created_at": "2023-11-23T11:03:00.000",
"updated_at": "2023-11-23T11:03:00.000"
}
},
{
"model": "comments.comment",
"fields": {
"title": "4個目のコメント",
"body": "コメントの本文4",
"owner_id": 2,
"created_at": "2023-11-23T11:04:00.000",
"updated_at": "2023-11-23T11:04:00.000"
}
},
{
"model": "comments.comment",
"fields": {
"title": "5個目のコメント",
"body": "コメントの本文5",
"owner_id": 1,
"created_at": "2023-11-23T11:05:00.000",
"updated_at": "2023-11-23T11:05:00.000"
}
},
{
"model": "comments.comment",
"fields": {
"title": "6個目のコメント",
"body": "コメントの本文6",
"owner_id": 2,
"created_at": "2023-11-23T11:06:00.000",
"updated_at": "2023-11-23T11:06:00.000"
}
},
{
"model": "comments.comment",
"fields": {
"title": "7個目のコメント",
"body": "コメントの本文7",
"owner_id": 1,
"created_at": "2023-11-23T11:07:00.000",
"updated_at": "2023-11-23T11:07:00.000"
}
},
{
"model": "comments.comment",
"fields": {
"title": "8個目のコメント",
"body": "コメントの本文8",
"owner_id": 2,
"created_at": "2023-11-23T11:08:00.000",
"updated_at": "2023-11-23T11:08:00.000"
}
},
{
"model": "comments.comment",
"fields": {
"title": "9個目のコメント",
"body": "コメントの本文9",
"owner_id": 1,
"created_at": "2023-11-23T11:09:00.000",
"updated_at": "2023-11-23T11:20:00.000"
}
},
{
"model": "comments.comment",
"fields": {
"title": "10個目のコメント",
"body": "コメントの本文10",
"owner_id": 2,
"created_at": "2023-11-23T11:10:00.000",
"updated_at": "2023-11-23T11:10:00.000"
}
}
]
すでにリセット済みのデータベースにデータを登録します.このときユーザのデータを先に登録しなければならないことに注意してください.この理由は順序を間違えるとリレーションシップの参照整合性に違反するからです.つまり,コメントを先に登録しようとしたときに,owner_id の参照ができないためにエラーが発生します.
...\django_comment_api>python manage.py loaddata comments\fixtures\user-data.json ⏎ Installed 3 object(s) from 1 fixture(s) ...\django_comment_api>python manage.py loaddata comments\fixtures\comments-data.json ⏎ Installed 10 object(s) from 1 fixture(s) ...\django_comment_api>
SQLite を操作して,二つのテーブルにデータが登録されていることを確認します.さらに内部結合 (INNER JOIN) ができることも確認しておきます.
sqlite> select id, username, email from auth_user; ⏎ id|username|email 1|user_a|a@sample.com 2|user_b|b@sample.com 3|user_c|c@sample.com sqlite> select * from comments_comment; ⏎ id|title|body|created_at|updated_at|owner_id 1|最初のコメント|コメントの本文|2023-11-23 11:01:00|2023-11-23 11:01:00|1 2|2個目のコメント|コメントの本文2|2023-11-23 11:02:00|2023-11-23 11:02:00|2 3|<3個目>のコメント|<h1>コメントの本文3</h1>|2023-11-23 11:03:00|2023-11-23 11:03:00|1 4|4個目のコメント|コメントの本文4|2023-11-23 11:04:00|2023-11-23 11:04:00|2 5|5個目のコメント|コメントの本文5|2023-11-23 11:05:00|2023-11-23 11:05:00|1 6|6個目のコメント|コメントの本文6|2023-11-23 11:06:00|2023-11-23 11:06:00|2 7|7個目のコメント|コメントの本文7|2023-11-23 11:07:00|2023-11-23 11:07:00|1 8|8個目のコメント|コメントの本文8|2023-11-23 11:08:00|2023-11-23 11:08:00|2 9|9個目のコメント|コメントの本文9|2023-11-23 11:09:00|2023-11-23 11:20:00|1 10|10個目のコメント|コメントの本文10|2023-11-23 11:10:00|2023-11-23 11:10:00|2 sqlite> SELECT ...> comments_comment.id, ...> comments_comment.title, ...> auth_user.username ...> FROM ...> comments_comment ...> INNER JOIN ...> auth_user ...> ON ...> comments_comment.owner_id = auth_user.id; ⏎ id|title|username 1|最初のコメント|user_a 2|2個目のコメント|user_b 3|<3個目>のコメント|user_a 4|4個目のコメント|user_b 5|5個目のコメント|user_a 6|6個目のコメント|user_b 7|7個目のコメント|user_a 8|8個目のコメント|user_b 9|9個目のコメント|user_a 10|10個目のコメント|user_b sqlite>
さらに API からの出力結果に owner_id を表示したいので,serializers.py を編集します.
comments\serializers.py
from .models import Comment
from rest_framework import serializers
class CommentSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Comment
fields = ['id', 'owner_id', 'title', 'body', 'updated_at']
def validate_title(self, value):
if len(value) > 10:
raise serializers.ValidationError("タイトルの最大文字数は10文字です")
return value
def validate_body(self, value):
if len(value) > 15:
raise serializers.ValidationError("本文の最大文字数は15文字です")
return value
実際に curl
コマンドで確認します.GET リクエストでは owner_id が正しく出力されていることがわかります.
...\django_comment_api>curl -u user_a:password http://127.0.0.1:8000/comments/ ⏎ {"count":10,"next":"http://127.0.0.1:8000/comments/?page=2","previous":null,"results":[{"id":9,"owner_id":1,"title":"9個目のコメント","body":"コメントの本文9","updated_at":"2023-11-23T11:20:00"},{"id":10,"owner_id":2,"title":"10個目のコメント","body":"コメントの本文10","updated_at":"2023-11-23T11:10:00"}]} ...\django_comment_api>curl -u user_a:password http://127.0.0.1:8000/comments/1/ ⏎ {"id":1,"owner_id":1,"title":"最初のコメント","body":"コメントの本文","updated_at":"2023-11-23T11:01:00"} ...\django_comment_api>
さらに,新規投稿時に投稿者の ID を記録する必要がるので,views.py に perform_create
関数を追加します.
comments/views.py
from django.shortcuts import render
from rest_framework import generics
from rest_framework import permissions
from .models import Comment
from .serializers import CommentSerializer
from .paginations import LargeResultsSetPagination
# Create your views here.
class CommentList(generics.ListCreateAPIView):
queryset = Comment.objects.all()
serializer_class = CommentSerializer
pagination_class = LargeResultsSetPagination
permission_classes = [permissions.IsAuthenticated]
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
class CommentDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Comment.objects.all()
serializer_class = CommentSerializer
permission_classes = [permissions.IsAuthenticated]
次に,POST リクエストを検証します.user_a と user_b のアカウントを使って順番に投稿したところ,owner_id が正しく設定されていることもわかりました.
...\django_comment_api>curl -X POST -d "title=owner" -d "body=store owner_id" -u user_a:password http://127.0.0.1:8000/comments/ ⏎ {"id":11,"owner_id":1,"title":"owner","body":"store owner_id","updated_at":"2023-11-23T17:28:40.544771"} ...\django_comment_api>curl -X POST -d "title=owner" -d "body=store owner_id" -u user_b:password http://127.0.0.1:8000/comments/ ⏎ {"id":12,"owner_id":2,"title":"owner","body":"store owner_id","updated_at":"2023-11-23T17:28:50.507987"} ...\django_comment_api>curl -u user_a:password http://127.0.0.1:8000/comments/ ⏎ {"count":12,"next":"http://127.0.0.1:8000/comments/?page=2","previous":null,"results":[{"id":12,"owner_id":2,"title":"owner","body":"store owner_id","updated_at":"2023-11-23T17:28:50.507987"},{"id":11,"owner_id":1,"title":"owner","body":"store owner_id","updated_at":"2023-11-23T17:28:40.544771"}]} ...\django_comment_api>
SQLite を使って投稿されたコメントに正しい owner_id が設定されていることを確認します.
sqlite> select id, title, owner_id from comments_comment; id|title|owner_id 1|最初のコメント|1 2|2個目のコメント|2 3|<3個目>のコメント|1 4|4個目のコメント|2 5|5個目のコメント|1 6|6個目のコメント|2 7|7個目のコメント|1 8|8個目のコメント|2 9|9個目のコメント|1 10|10個目のコメント|2 11|owner|1 12|owner|2 sqlite>
Web ブラウザでも確認すると良いでしょう.
これで投稿時に owner_id を設定することができるようになりました.しかしながら,現時点では他のユーザが投稿したコメントを更新・削除したりできてしまうという状況で,多くの現実的なシステムではこれは問題になるでしょう.次のページから2種類のケースを想定した設定を行います.