【Part 1】では各種基礎知識について学びました。【Part 2】では実際にDjango REST Framework(以下、DRF)部分の開発をしていきましょう。なおNext.js部分は【Part 3】でのご紹介となります。
まずはDRFで開発するための環境を作っていきます。なお環境構築についてはDjango REST Frameworkの環境構築の記事で紹介したことがあるので細かい説明は省略しますが、ターミナルでの作業も一から実施していきます。
過去記事ではディレクトリ作成とプロジェクト作成をそれぞれ別のセクションで解説していましたが、ここでは早速省略します。
blog_project
)とApp(blog_app
)を作成してDRFが始められるための準備をします。ここまで完了すると、DRFプロジェクトのディレクトリは上の通りになっていると思います。
環境構築が完了したら、はじめにProject(blog_project
)側を設定していきましょう。
Project(blog_project
)ではsettings.py
ファイル→.env
ファイル→.gitignore
ファイル→urls.py
ファイルの順に編集します。
blog_project/settings.py
の設定にあたって、まずは使用するライブラリをインストールします。
blog_project/settings.py
)から分離して参照させるために使用するモジュール。上の通りライブラリがインストールできたら、blog_project/settings.py
を編集していきます。
環境構築時、django-admin startproject
コマンドでProject(blog_project
)を作成するとblog_project/settings.py
にはすでにコードが書かれているので、「# 追記」とある行はそのまま新規入力、「# 修正前」・「# 修正後」と記載がある行では、「# 修正前」の行を削除し「# 修正後」の行を新規入力してください。特に大事だと思うポイントに絞って解説します。
django-admin startproject
コマンド実行後、Djangoプロジェクトごとに生成される一意の秘密鍵です。Django起動時には必須のキーであるため、セキュリティ上の観点からconfig
を利用して.env
ファイルを参照させることでSECRET_KEY
の値を秘匿化しておきます。True
となっていますが、本番環境へのデプロイ時にはセキュリティ上の観点からFalse
にしておく必要があります。SECRET_KEY
同様、config
を利用して.env
ファイルを参照させることでDEBUG
の値を秘匿化しておきます。INSTALLED_APPS
はその名の通りDjango・DRFプロジェクトにおいてインストール済みのアプリ・ライブラリをまとめたリストです。django.contrib
から始まるアプリはデフォルトで設定されているものです。それぞれパスを辿れば、後ほど作成するApp(blog_app
)と同様の構造になっているのが確認できるはずです。
pip
でインストール後に追記が必要になります。
blog_app
)名になりますが、こちらはblog_app/app.py
ファイルの内の値を参照してください。MIDDLEWARE
とは、アプリケーション前後のリクエスト・レスポンスの処理が記載されているリストです。django-cors-headers
をインストール後 (l.22) でcorsheaders
を追加して、許可したオリジンからのリクエストのみに制限するオリジン間リソース共有(Cross-Origin Resource Sharing)を設定しましたが、それに関わる処理を実行するためにMIDDLEWARE
においても上の通り追記が必要になります。なお (l.31) を除いた (l.30-36) のMIDDLEWARE
はデフォルトで設定されています。CORS_ORIGIN_WHITELIST
を設定することで、特定ドメインからのリクエストのみを許可できます。今回は、Next.jsプロジェクトからのリクエストのみを受け付けられるように、Next.jsが起動するローカルサーバのポート番号3000
と設定しました。IsAuthenticated
という権限クラスをデフォルトで適用するよう記述しているので、views.py
ファイルで設定されない限りは未認証のユーザーはAPIへのアクセスが制限されます。WSGI
はWeb Server Gateway Interface
の略語でウィスギーと読みます。従来、Djangoを含むWebフレームワークを利用する際には使用できるWebサーバが制限されていましたが、その問題を解決するためにWebアプリケーションとWebサーバを接続する標準仕様がWSGIによって定められました。WSGIに対応したWebアプリケーション(やフレームワーク)は、WSGIに対応した任意のWebサーバ上で運用できるようになるため、アプリケーション側がWSGIに対応していれば、アプリケーションのコードに修正を加えることなく、WSGI対応サーバを自由に選択することができます(※参考:Wiki)そしてこのDjango・DRFプロジェクト(一般用語では「Webアプリケーション」)がWSGIを介してWebサーバとやり取りする際の出入り口としてパスを指定するのが、WSGI_APPLICATION
設定です。長々と解説しましたが、これもデフォルトで設定されています。なお、WSGI自体はアプリケーションサーバで動きますが、今回はgunicornの設定を想定しています。blog_project/settings.py
設定時にも触れましたが、.env
ファイルで環境変数を設定します。まずは下のコマンドでファイルを作成しましょう。
ここで一緒に.gitignore
ファイルも作成していますが、(こちらは今回説明は省略しますが、)GitHub等でコードを管理する場合に、GitHub上にプッシュしたくないディレクトリ・ファイルを定義するファイルです。せっかくなのでこちらも一緒に設定しましょう。
.env
ファイルには、blog_project/settings.py
で秘匿化したいSECRET_KEY
とDEBUG
について記載します。
せっかく秘匿化したいのにGitにプッシュされてしまっては意味がないので、.gitignore
ファイルでは、上のように記載します。他にも仮想環境のblog_virtual
ディレクトリ・キャッシュの__pycache__
・データベース(以下、DB)のdb.sqlite3
も記載しておきましょう。これでblog_project/settings.py
の設定は完了です。
続いて、blog_project/urls.py
の設定を実施します。このファイルでは、URLと関連するViewの紐付けを、urlpatterns
というリストで定義します。
blog_app
を作成しますが、blog_app/urls.py
経由でblog_app
内にあるView(blog_app/views.py
)にアクセスするための設定をしています。パス名は自由に設定できますが、今回はブログ作成なので分かりやすくblog/
としました。これで、Project(blog_project
)側の設定は完了しました。続いて、App(blog_app
)側の設定に移ります。まずはApp(blog_app
)側のディレクトリに移動して、必要なファイルを作成します。
ここではurls.py
ファイルとserializers.py
ファイルを作成しましょう。それぞれのファイルについての説明は各ファイルの設定欄にあります。
App(blog_app
)では、models.py
ファイル→serializers.py
ファイル→views.py
ファイル→urls.py
ファイル→admin.py
ファイルの順に編集します。
blog_app/models.py
ファイルは、DBのテーブルをPythonのクラスとして定義するためのファイルです。Djangoで提供されているModelクラスを継承して、DBテーブルのカラムと対応するフィールドを作成していきます。フィールド作成時にはフィールドの型を定義します。型には様々な種類がありますが、その中で今回利用するフィールドの型は以下の通りです。また型には一部必須のものも含めて()内にオプションを追加することができますが、それは後述します。
CharField()
:文字列(テキスト)データを入力できるフィールドです。最も一般的なフィールドと言っても良いでしょう。SlugField()
:URLの文字列を入力できるフィールドです。通常アルファベット・数字・ハイフンの組み合わせで構成され、スペースや特殊文字は利用しません。IntegerField()
:整数の値を入力できるフィールドです。数量を表現する際などに使われ、正・負・0を格納できます。ForeignKey()
:1対多(ManyToOne)の関係を表現できるフィールドです。一般的な外部キー参照フィールドです。ManyToManyField()
:多対多(ManyToMany)の関係を表現できるフィールドです。タグ付け等に利用できます。MarkdownxField()
:Markdown形式のテキストを入力できるフィールドです。今回のブログ作成の肝です。上記フィールドの型を使ってModelを作成していきましょう。
models
をインポートします。blog_project/settings.py
設定前にdjango-markdownx
をインストールしましたが、その中からMarkdownxField
をインポートして、デフォルトでは提供されていないMarkdownを記述できるフィールドの型を準備します。models
をインポートしましたが、そのmodels.Model
を継承してModelを作成します。
f_category
という変数を用意しCharField
をインスタンス化します。CharField
を利用する場合、max_length
として文字列の長さ指定が必須ですが、ここではNull値を許容するnull=True
とデータ追加時の空白を許容するblank=True
を加えます。特にnull
オプションの設定については、一度Modelをマイグレーションしデータを追加した後に、さらにフィールドを追加する際に利用するものになります。f_category
フィールドに追加された値が表示されます。Entry
モデル)を作成します。
body
フィールドは、記事の本文としてMarkdown形式でテキストを入力するフィールドです。オプションは特に不要ですが、MarkdowxFieldを型として指定します。ForeignKey
というフィールドの型を利用してFCategory
モデルを参照します。このフィールドではon_delete=models.SET_NULL
をオプションに指定していますが、これにより親モデルであるFCategory
モデルが削除された場合でも、Entry
モデル内のcategory
フィールドがnull
に変わるためデータの整合性を保つことができます。また、同時にNull値を許容するためにもnull=True
が設定されている必要があります。(l.23) ではManyToManyField
というフィールドの型を利用してFTag
モデルを参照します。is_publick
というフィールドを作成しています。IntegerField
というフィールドの型とchoices
というオプションを利用することで、0を選択中の時には公開、1を選択中の時には非公開としています。説明が長くなりましたが、以上で、blog_project/settings.py
の設定完了です。
blog_app/serializers.py
はDjangoにはないDRF特有のもので、作成したModel内のデータをJSON / XML形式に変換(シリアライズ)してAPIでレスポンスしたり、ユーザーから送られてくるJSON等の形式のデータをPythonオブジェクトに変換(デシリアライズ)したりする役割を持ちます。DjangoにおけるForm
と考えてみてください。
rest_framework
からserializers
をインポートします。blog_app/models.py
から作成したModelをインポートします。serializers.ModelSerializer
は、Serializerの中でも最も一般的なものと言っても良いでしょう。1) Djangoで作成したModelに対してしかシリアライズできない点、2) (l.6) class Meta
内で動作を指定する点、が特徴です。※他にもserializers.Serializer
というものもありますが、こちらはModel以外でもオブジェクトであればシリアライズ可能です。
FCategory
モデルを変数model
に格納します。
FCategory
モデル内のどのフィールドをシリアライズするか指定します。ここではすべてのフィールドを指定したいので__all__
としますが、任意のフィールドのみ指定することもできます。Entry
モデルに対してシリアライズするよう設定していきます。Entry
モデルはFCategory
モデルとFTag
モデルを参照しているため、(l.16-17) の通り他のSerializerとは別の設定が必要になります。
Entry
モデル内のcategory
フィールドに対して、(l.5) で作成したFCategorySerializer
を指定します。tag
フィールドについてはManyToMany
のため、引数にmany=True
を指定しましょう。lookup_field = 'slug'
とした上でextra_kwargs
で追加設定を付与しています。これによりEntry
モデルの各レコードが、id
ではなくslug
フィールドの値を使用してURLを構築するよう設定しています。他にもextra_kwargs
では指定したフィールドに対してread_only
やwrite_only
を指定することもできます。例えば、read_only
を指定するとAPIでの読み込みは可能だが書き込みはできないようになるので意図しない更新を防ぐことができます。write_only
については、パスワード情報等の読み込みをすべきでないフィールドに対してよく使われます。Serializerの作成後はView(blog_app/views.py
)の編集です。DRFプロジェクトにおいては、APIエンドポイント(≒ URL)ごとに異なる振る舞い・ロジックを指定できます。ここまでの設定事項を整理し役割を明確化すると、Model(blog_app/models.py
)からデータを取得後、Serializer(blog_app/serializers.py
)でデータをJSON形式に変換し、View(blog_app/views.py
)でレスポンスを生成するという流れになります。そのViewの数ですが、かなり多いので全部覚える必要はありませんが、「作成 / 取得 / 更新 / 削除」と「詳細 / 一覧」から必要に応じて使い分けする必要があります。今回は「一覧+詳細」の「取得」をしたいので、ListAPIView
とRetrieveAPIView
を使いましょう。
ListAPIView
とRetrieveAPIView
はいずれもジェネリックビューに分類されるので、generics
をインポートしましょう。ちなみに、ジェネリックビュー(generics
)は上の機能ごとにViewが提供されていますが、それ以外にも機能がまとめて提供されているビューセット(viewsets
)というViewもあります。rest_framework
から、今度はfilters
をインポートします。これによりクエリパラメータを使用してデータをフィルタリングすることができます。詳細は (l.23) で解説します。FCategory
モデル・FTag
モデルともに一覧表示だけできれば良いので、ここではListAPIView
を利用します。
queryset
を指定します。queryset
とは、Viewがデータを取得・表示する際の元となるデータソースです。今回は各ModelをもとにしてViewを作成しているので、全レコードを取得するためにモデル名.objects.all()
とします。blog_app/serializers.py
で設定したものを使いましょう。permission_classes
を利用してアクセス制御をしています。blog_project/settings.py
の (l.42) でデフォルトのアクセス制御をかけてみましたが、今回はサンプルなので誰でもアクセスできる(AllowAny)
を指定しましょう。filters
について、検索キーワードからフィルタリングできるようにするため、フィルターにはfilters.SearchFilter
を設定します。なお、filters.OrderingFilter
というフィルターを利用すると、並び替えができるようになります。'title', 'slug', 'body'
と設定したので、Entry
モデルにおける対象のフィールドの中からフィルタリングができるようになります。Entry
モデルにおいて、「詳細」の「取得」ができるよう設定します。RetrieveAPIView
は詳細情報の取得が可能であるため、識別子が必要になります。デフォルトではid
でレコードを識別しますが、lookup_field = 'slug'
とすることで、id
の代わりにslug
を識別子に変更できます。続いてApp(blog_app
)内でblog_app/urls.py
の設定をしましょう。こちらはProject(blog_project
)側のblog_project/urls.py
とは別物です。
path
関数をインポートします。これにより各URLに対してどのViewを紐付けるか定義できます。第一引数にURLパターンの任意の文字列、第二引数にblog_app/views.py
で作成したViewクラスを指定します。urlpatterns
リストにURLパターンを追加します。ここで各URL内のViewクラスに対して.as_view()
メソッドを追加していますが、これはurlpatterns
リスト内では関数しか指定できないので、.as_view()
メソッドで関数ベースのViewに変換する必要があるためです。こちらがApp(blog_app
)側の最終ステップです。blog_app/admin.py
では、Django・DRFプロジェクトの管理サイトの設定を行うためのファイルです。カスタマイズをすることで管理サイトにおけるデータの表示形式の変更等もできますが、今回はblog_app/models.py
で作成したModelを管理サイトに登録するだけにとどめておきます。これによって管理サイト上でのデータの表示追加更新削除が簡単にできるようになります。
admin.site.register(モデル名)
とすることで、管理サイトに簡単にModelを登録することができます。これで今回のブログ作成のためのPythonファイル編集は完了しました。
DRFプロジェクト作成の最終章です。ここではマイグレーション、データ入力、APIの動作確認を実施してDRFプロジェクト側の完成となります。
マイグレーションとは、blog_app/models.py
で作成したModelをDBのスキーマ(構造)に反映させる作業です。今回Django標準のSQLiteというDBを使用していますが、現時点ではDB内にはデータもなければそのデータを入れる先のテーブルもカラムすらないので空っぽです。そのため、このマイグレーションという作業が必要になります。順番としては、まずmakemigrations
で「マイグレーションファイル」を作成してから、それをmigrate
でDBに反映(マイグレーション)させるという流れです。
python manage.py makemigrations
でマイグレーションファイルを作成します。blog_app/migrations/0001_initial.py
というパスで0001_initial.py
というファイルができたことが分かります。このファイルこそがマイグレーションファイルです。ちなみに、2回目以降マイグレーションを実施すると過去のマイグレーションファイルに対する差分という形で、例えば0002_xxx.py
という形式でマイグレーションファイルが作成されていきます。- Create model xxx
とModelが3つ続いて表示され、それぞれがマイグレーションファイル内に作成されたことが分かります。python manage.py migrate
で、0001_initial.py
をマイグレーションさせます。ここでマイグレーションファイルは指定しない場合には、blog_app/migrations/
ディレクトリにあるすべてのマイグレーションファイルが反映されることになります。blog_app
以外作ってないぞ!?」と思われたかもしれません。そんな方はよく思い出してください。blog_project/settings.py
のINSTALLED_APPS
という項目で、デフォルトで設定されているアプリがあったことを。その中でDBへの反映が必要なデフォルトアプリは、各アプリ内ですでにマイグレーションファイルが作成されているので、このタイミングで一緒にマイグレーションされるわけです。下にもあるように、DRFプロジェクト作成後最初にローカルサーバを立ち上げた時に表示される、「18のマイグレーションファイルが適用されていません」、というのはこれのことだったんですね。ちなみに、これらのアプリはblog_virtual/lib/python3.10/site-packages/django/contrib
内にそれぞれ格納されているので興味があれば確認してみてください。参考までに、以下DBにマイグレーションされるアプリの説明をまとめておきます。admin
:Djangoの管理者サイトに関連するアプリのマイグレーションです。管理者サイトの設定やログ記録などに関連するテーブルのスキーマ変更が含まれています。auth
:ユーザー認証に関連するアプリのマイグレーションです。ユーザーやグループ、パーミッションなどの情報を管理するためのテーブルのスキーマ変更が含まれています。contenttypes
:コンテンツタイプフレームワークに関連するマイグレーションです。Djangoアプリケーション内で使用されるModelとそのタイプに関する情報を管理するためのスキーマ変更が含まれています。sessions
:セッション管理に関連するマイグレーションです。Webセッションのためのテーブルが作成され、セッションデータの保存に使用されるスキーマ変更が含まれています。Applying xxx.000X... OK
という形式で反映されていることが分かります。例えば (l.12) のマイグレーションファイルについてはblog_virtual/lib/python3.10/site-packages/django/contrib/admin/migrations
を覗けば見つかります。全てApply
されればマイグレーション完了ですが、ここで勉強のためにも作成したblog_app/migrations/0001_initial.py
というマイグレーションファイルの中身を確認してみましょう。
initial = True
となっています。2回目以降のファイルには表記はありません。dependencies
リストの中身は空です。しかし、2回目以降の実施ではリストの中にタプル型で対象のAppとマイグレーションファイルが、('blog_app', '0001_initial.py')
といった形式で記載されます。operation
とはスキーマに対する変更内容がまとめられた部分になります。Modelを反映しているので当然ですがblog_app/models.py
と似たような表示になっていることが分かりますね。
.CreateModel
というメソッドが使われていることが分かります。他にも、Model自体が削除された場合には.DeleteModel
メソッド、Model内でフィールドが追加された場合には.AddField
メソッド、フィールドが削除された場合には.RemoveField
メソッドが使われます。blog_app/models.py
での設定項目との差分がありますね、id
です。id
はModel作成時には明示的に作成していませんでしたが、実は裏で作られているんですね。識別子がないとDBとして使えないので、当然は当然です。こちらはUUID
への変更も可能です。DRFプロジェクトとしては一旦完成し、DBへの反映も終わったところで、早速データを入力していきましょう。いつも通りpython manage.py runserver
でローカルサーバを立ち上げてください。すると(画像ではblog/
のパスを開いてしまっていますが、)画像下赤枠の通りadmin/
というパスとその他にblog_app/urls.py
ファイルで作成したblog/
が先頭についたパスがいくつかあるのが分かります。
まずはデータを入力したいので
admin/
から管理サイトにいきたいところですが、admin/
パスに遷移するとusername
とpassword
が求められると思います。でも作った覚えがないですよね?そうです、作らないといけないのです。下のようにターミナルに戻って下の順序通り作りましょう。
python manage.py createsuperuser
とコマンドを入力すると、最も強い権限を持つスーパーユーザーが作れます。xx
となっていますが、ここではblog_poster
という名前で作ってみます。admin/
の画面で入力してください。下の画像の通り管理サイトにログインできるはずです。ここではAPIの動作確認ということで、blog_app/urls.py
ファイルで作成した全てのURLにアクセスしてみます。blog_app/urls.py
ファイルで設定した通り各URLはViewと接続しているので、問題なく各リソースにアクセスできれば、今回は一旦動作確認完了としたいと思います。
それでは、まず/blog/all-entries
にアクセスします。入力したデータが下画像の通り確認できると思います。
引き続き上画像において、同URL内の画面上部に
Fillters
というボタンがありますが、これを押すと下画像のようにダイアログが表示されると思います。blog_app/views.py
でfilters.SearchFilter
を使って検索キーワードによるフィルタリングを実装しましたが、それの動作確認のためにダイアログには"gramm"という文字列を入力してみました。Search
ボタンを押してみると、1件だけレコードが引っかかったようです。レスポンスを見るとslug
にpython-grammer
と登録していたのですが、blog_app/views.py
ではslug
も検索対象にするよう設定していたので、これがフィルタリングされ表示されたようですね。これで問題なくフィルタリング機能が実装されていることが確認できました。ちなみに検索をすると表示のようなURL(/?search=検索キーワード
)に変わっていることが分かります。
ここまで一覧情報を確認しましたが、最後に詳細情報についても確認しましょう。2つ上の画像の
"id": 2
にある通りRustについての記事を作成しています。このデータのslugであるabout-rust
を取ってきて、/blog/each-entry/
の後に付けてアクセスしましょう。すると、下の画像の通り詳細情報が表示されたと思います。問題なくアクセスできましたね。
これでDRFプロジェクト側の設定は全て完了です。
DRFプロジェクト側の設定、お疲れ様でした。続いて、Next.jsプロジェクト側の設定に移りましょう。