このページでは、Django での CRUD の実現方法について解説していきます!
Contents
CRUD
まずは、CRUD とは何なのか?
この点について解説していきたいと思います。
CRUD とは
CRUD は、データベースに対する基本的な4つの操作の頭文字を並べた用語になります。
具体的には、下記の4つの操作の頭文字を並べた用語になります。操作対象は、データベースのテーブルのレコード(モデルクラスのインスタンス)となります。
- Create:レコードの登録
- Read:レコードの参照
- Update:レコードの更新
- Delete:レコードの削除
ウェブアプリではデータベースの操作を行うものが多いため、ウェブアプリ内で管理するデータに対する操作と考えても良いと思います。ちなみに、CRUD の読み方は「クラッド」になります。
ユーザーに対する CRUD
例えば、X (Twitter) アプリでの、ユーザーに対する CRUD について考えてみましょう!
この場合、ユーザーに対する C (Create) は「ユーザー登録」となります。X でツイートするためにはユーザー登録が必要ですよね。このユーザーの登録は、ユーザーを管理するテーブルに対してレコードを登録(新規作成)することで実現することができます。
また、ユーザーに対する R (Read) は「ユーザーの情報の表示」となります。X であれば、自分自身や他のユーザーのプロフィール等を表示して確認することができますね!このユーザーの情報の表示は、ユーザーを管理するテーブルからレコードを参照(取得)することで実現できます。
次に、ユーザーに対する U (Update) は「ユーザーの情報の変更」となります。X であれば、アイコン(サムネイル)画像の変更や、プロフィールの変更等を実施することが可能です。このユーザーの情報の変更は、ユーザーを管理するテーブルに保存済みのレコードを、各種フィールドの値を変更したレコードで上書きすることで実現できます。
さらに、ユーザーに対する D (Delete) は「ユーザーの退会」となります。X であれば、アカウント削除の操作によってユーザーの退会を行うことができます。このユーザーの退会は、ユーザーを管理するテーブルのレコードを削除することで実現できます。
このように、X においても、ユーザーというモデルクラスのインスタンスに対して CRUD 操作を行うことができるようになっています。こんな感じで、世の中のウェブアプリにおいては、各種データに対して CRUD 操作を行うことができるようになっているものが多いです。
もちろん、Django で開発するウェブアプリでも CRUD 操作を実現することが可能で、その実現方法を以降で解説していきます。
スポンサーリンク
CRUD 操作とリクエスト
また、前述の例からも分かるように、X におけるユーザーに対する CRUD 操作はユーザー(アプリ利用者)が実施できるようになっています。同様に、Django においても、必要に応じて CRUD 操作をユーザーが実施できるようにアプリを開発していくことになります。
また、ウェブアプリの操作や機能の実行は、リクエストをウェブアプリに対して送信することで実施されることになります。そして、ユーザーが実施したい操作・機能は、主にリクエストの URL とメソッドによって決まり、これらに応じた処理をウェブアプリが実行することになります。
そのため、各操作をユーザーが実行できるようにするためには、各操作に対して URL およびメソッドを割り当て、それぞれの URL とメソッドのリクエストが受け付けられるようにウェブアプリを開発していく必要があります。
このページでは、各操作に対して異なる URL を割り当て、さらに下記のメソッドを割り当てるようにすることを前提に解説していきたいと思います。
- Create:
POST
(GET
) - Read:
GET
- Update:
POST
(GET
) - Delete:
POST
(GET
)
括弧内に示したメソッドは、各操作の前に行う「フォームの表示」や「ページの表示」に対するリクエストのメソッドになります。例えば、レコードの新規登録や更新を行う際には、新規登録するレコードや、更新後のレコードの各種フィールドの値をユーザーに指定してもらうことが多いです。そして、その指定を実現するために、事前にフォームの表示が必要となります。
上記では、このフォームの表示を要求する際のリクエストのメソッドを GET
とし、データベースへのレコードの登録を要求する際のリクエストのメソッドを POST
としています。
このように、事前にフォーム等の表示が必要となる操作に対しては、データベースへの反映をリクエストするメソッドと、フォームやページの表示をリクエストするメソッドの2種類を割り当てておく必要があります。
このあたりに関しては、下記のフォームの解説で説明していますので、詳細に関しては下記ページを参照していただければと思います。
【Django入門5】フォームの基本また、上記の解説は、あくまでも「ウェブブラウザで HTML によって表示したフォームで CRUD を実現する場合」に限った内容となるので、その点に注意してください。
HTML の form
タグで表示したフォームから送信可能なリクエストのメソッドは GET
と POST
の2種類のみとなります。それに対し、CRUD は4種類の操作となるため、これらの各操作それぞれに個別のメソッドを割り当てることができません。なので、CRUD の各操作は、メソッドではなく URL で区別できるようにする必要があります。つまり、CRUD の各操作を要求するリクエストには別々の URL を割り当てる必要があります。
なんですが、例えば JavaScript を利用して HTML の form
タグのフォーム以外からリクエストを送信するようにしている場合は、GET
と POST
以外のメソッドのリクエストも送信可能となります。そして、このような場合は、下記のように CRUD 操作に個別のメソッドを割り当てることも可能となります。
- Create:
POST
- Read:
GET
- Update:
PATCH
orPUT
- Delete:
DELETE
この場合、リクエストされた操作をメソッドから区別できるようになり、URL で区別する必要が無くなります。なので、URL には各種操作に対して同じものを設定することができます。
例えば、コメントのレコードに対する操作であれば、全 CRUD 操作に対するリクエストの URL として /comment/
を割り当て、リクエストされた操作はメソッドから判断してやれば良いことになります。
この Django 入門 の連載では、ここまでウェブブラウザの HTML で表示されるページからウェブアプリの操作を行うことを前提に解説してきているので、前述の通り、まずは GET
と POST
のみのメソッドを各 CRUD 操作に割り当てて解説していきます。ただし、今後 API 等の解説を行う機会もあると思いますので、その際には、GET
と POST
以外のメソッドを CRUD 操作に割り当てる例も示していきたいと思います。
少し話が逸れましたが、重要なのは、CRUD 操作をユーザーから実施できるよう、各操作に URL とメソッドを割り当て、それらの URL とメソッドのリクエストを受け付けられるようにウェブアプリを開発することです。で、この受け付け可能なリクエストに関しては urls.py
で定義することが可能です。これに関しては、下記ページで解説していますので、詳細を知りたい方は下記ページを参照していただければと思います。
さらに、それらのリクエストを受け付けたときに、そのリクエストに応じて CRUD 操作が行われるようにウェブアプリ(ビューやテンプレートファイルなど)を開発することも重要です。これに関しては、後述の CRUD の実現方法 で解説を行います。
CRUD 操作の利用制限
また、CRUD 操作は、単にこれらが実施できるようにするだけでなく、適切に「CRUD 操作の利用制限」を行うことも重要となります。
例えば X で、全ツイートに対する Delete 操作を全ユーザーが実施できるとしたら、あなたがポストしたツイートも他のユーザーに削除されてしまう可能性があります。こういったことが起こらないように、基本的には、ツイートに対する Delete 操作は、そのツイートをポストしたユーザーやウェブアプリ管理者以外は実施できないように制限されているはずです。
このように、ウェブアプリでは、レコードに対する Update 操作や Delete 操作は、特定のユーザーのみ実施できるように制限されていることが多いです。Django でウェブアプリを開発する場合も、こういった操作の利用制限を行う必要があります。
具体的な制限方法に関しては CRUD 操作の利用制限の実現 で解説しますが、まずは、こういった操作の利用制限が必要であることを頭に入れておいてください。
CRUD の実現方法
続いて、CRUD の実現方法について解説していきます。
ここまでの Django 入門 の連載の中では、基本的には CRUD の Create と Read、すなわちインスタンスの登録と参照についてのみ解説を行ってきましたが、ここで、Django での CRUD の全ての操作の実現方法について解説していきたいと思います。
これらの CRUD の操作の実現は、Django 入門 の前回の連載(下記ページ)で紹介したクラスベースビューを利用することで簡単に実現可能です。
【Django入門15】クラスベースビューの基本ただ、クラスベースビューでは、どういった処理の流れによって CRUD 操作を実現できるのかが分かりにくいので、処理の流れの実装も必要となる関数ベースビューでの実現方法も併せて説明していきたいと思います。
また、以降の解説では、CRUD を実現するビュー・テンプレートファイルのコードの紹介も行っていきます。紹介するコードは、下記のような前提に基づいたものになっていますので、その点にご留意ください。
まず、アプリ名は crud_app
であることを前提としたコードとなっています。
また、下記のように、コメントを管理するモデルクラス Comment
が models.py
に定義されており、
from django.db import models
class Comment(models.Model):
text = models.CharField(max_length=256)
下記のように、Comment
の各種フィールドの値の入力受付を行うフォーム CommentForm
が forms.py
に定義されていることを前提としたコードとなっています。
from django import forms
from .models import Comment
class CommentForm(forms.ModelForm):
class Meta:
model = Comment
fields = ['text']
さらに、プロジェクトの urls.py
が下記であることを前提としたコードとなっています。コードとして、関数ベースビューのものとクラスベースビューのものを紹介していきますが、それぞれの場合で urls.py
が異なることを前提としているので注意してください。
また、このあたりに関しては後述でも解説しますが、特に Update 操作と Delete 操作、さらには1つのレコードに対する Read 操作に関しては、操作対象となるレコードの情報をクライアントから指定できるようにする必要があります。そのため、下記のように、これらの操作に対応する URL では <int:pk>/
部分で操作対象となるレコードのプライマリーキーを指定できるようにしています。
from django.urls import path
from crud_app import views
urlpatterns = [
path('create/', views.comment_create, name='create'),
path('read_detail/<int:pk>/', views.comment_read_detail, name='read_detail'),
path('read_list/', views.comment_read_list, name='read_list'),
path('update/<int:pk>/', views.comment_update, name='update'),
path('delete/<int:pk>/', views.comment_delete, name='delete'),
]
from django.urls import path
from crud_app import views
urlpatterns = [
path('create/', views.CommentCreate.as_view(), name='create'),
path('read_detail/<int:pk>/', views.CommentReadDetail.as_view(), name='read_detail'),
path('read_list/', views.CommentReadList.as_view(), name='read_list'),
path('update/<int:pk>/', views.CommentUpdate.as_view(), name='update'),
path('delete/<int:pk>/', views.CommentDelete.as_view(), name='delete'),
]
では、ここから、CRUD の各操作の実現方法の解説を行っていきます。
ここまで説明してきたように、CRUD 操作とはデータベース操作となります。ただし、CRUD 操作をユーザーが利用できるようにするためには、これらの操作が実現できるように適切にビューを作る必要もあります。そのため、ここからは、各操作を実現するための「データベース操作」、「ビューでの処理の流れ」、さらには「ビューやテンプレートファイルの実装例」を示しながら、解説を進めていきたいと思います。
スポンサーリンク
C (Create) の実現
まず、CRUD の C (Create) の実現方法について解説していきます。
Create とはテーブルへのレコードの新規登録で、レコードの新規登録とは、まだ存在しないレコードのテーブルへの保存処理のことになります。
Create のデータベース操作
まずは、Create を行うためのデータベース操作について解説していきます。
Django においては、データベースの操作は、モデルクラスのインスタンスによるメソッドの実行や、モデルクラスのマネージャーによるメソッドの実行によって実施可能です。このあたりに関しては下記ページで解説していますので、忘れてしまった方は下記ページを参照してください。
【Django入門6】モデルの基本この Create に関しては、モデルクラスのインスタンスをコンストラクタで新規に作成し、そのインスタンスに save
メソッドを実行させることで実現できます。これにより、そのモデルクラスのテーブルに、save
メソッドを実行したインスタンスと同じフィールドを持つ新たなレコードが新規登録されることになります。
instance = モデルクラス(各種フィールドの値)
instance.save()
また、Create は、モデルクラスのマネージャー objects
に create
メソッドを実行させることでも実現可能です。
モデルクラス.objects.create(各種フィールドの値)
基本的には、上記のようなデータベース操作によって Create を実施することができるのですが、ユーザーに Create 操作を提供する場合は、ユーザーにレコードの各種フィールドにセットする値をフォームで指定してもらうようにすることが多いです。この場合、そのフォームから送信されてきたフィールドの値を上記の 各種フィールドの値
に指定した上でレコードのテーブルへの保存を行うことになります。
また、そのフォームを「モデルフォームクラス」で実現している場合は、下記のような処理によっても Create を実施することが可能です。
form = モデルフォームクラス(フォームから送信されてきたデータ)
if form.is_valid():
form.save()
上記の1行目でフォームから送信されてきた各種フィールドの値と同じフィールドの値を持つフォームが生成されます。さらに、そのフォームがモデルフォームの場合、3行目の save
の実行によって、フォームにセットされた各種フィールドの値と同じフィールドの値を持つレコードがテーブルに新規登録されることになります(save
実行前には、上記の2行目のような、フォームから送信されてきた各種フィールドの値の妥当性の検証の実行が必要となります)。
このレコードの新規登録先のテーブルは、モデルフォームクラスの model
属性に指定したモデルクラスのものとなります。このあたりに関しては下記ページで詳しく解説していますので、忘れてしまった方は下記ページを参照してください。
Create のビュー
次に、Create 操作を行うビューの作り方について解説します。
基本的には、Create 操作自体に関しては、上記で紹介したいずれかの方法で実現可能です。ただし、ユーザーに Create 操作を提供するのであれば、ユーザーに新規登録するレコードの各種フィールドの値を指定してもらう必要があるため、それらを指定してもらうためのフォームの表示も必要になります。
したがって、Create 操作を行うビューでは、大きく分けて、フォームの表示、および、レコードの新規登録の2つの処理が必要となります。
より具体的には、メソッドが GET
のリクエストを受け取った時にフォームを表示するための HTML をボディとするレスポンスを返却し、メソッドが POST
のリクエストを受け取った時に、送信されてきた各種フィールドの値をセットしたレコードの新規登録を行うようにビューを作成する必要があります。
関数ベースビューでの Create の例
ここまでの解説を踏まえて作成した、Create 操作を実現する関数ベースビュー comment_create
の例は下記のようになります。
from django.shortcuts import render, redirect
from .forms import CommentForm
def comment_create(request):
if request.method == 'POST':
# 送信されてきたフィールドの値からフォームを生成
form = CommentForm(request.POST)
if form.is_valid():
# 送信されてきたデータが妥当である場合
# 送信されてきたフィールドの値をセットしたレコードを保存
form.save()
return redirect('read_list')
else:
# 空のフォームを生成
form = CommentForm()
context = {
'form': form
}
return render(request, 'crud_app/comment_create.html', context)
リクエストのメソッドが POST
以外の場合は、モデルフォームクラスの CommentForm
のコンストラクタを引数なしで実行して空のフォームを生成し、それを render
関数でテンプレートファイルに埋め込んでフォームの表示を行っています。また、リクエストのメソッドが POST
の場合は、Create のデータベース操作 の最後に示した方法でレコードの新規登録を実施しています。関数の第1引数が request
である場合、フォームから送信されてきたデータは request.POST
から取得可能です。
上記の render
関数で利用している comment_create.html
の例も下記に示しておきます(ヘッダーなどは省略しています)。
<form action="{% url 'create' %}" method="post">
{% csrf_token %}
<table>{{ form }}</table>
<p><input type="submit" value="送信"></p>
</form>
クラスベースビューでの Create の例
次は、Create 操作を行うクラスベースビューの例を示していきます。
Django では、Create 操作を行うクラスベースビューとして CreateView
が定義されています。そして、この CreateView
は、ここまで説明してきたようなデータベース操作やビューで必要な処理が実行されるように作られています。
なので、基本的には CreateView
を継承するクラスを定義するだけで、Create 操作を行うクラスベースビューが実現できます。ただし、Create 対象のモデルクラス(テーブル)や、利用するモデルフォームクラス等をクラス変数で定義してカスタマイズする必要はあります。
具体的には、先ほど示した関数ベースビューの comment_create
と同等の機能を持つクラスベースビュー CommentCreate
は下記の定義で実現できます。テンプレートファイルは、先ほど示した comment_create.html
をそのまま利用するようにしています。
from .models import Comment
from .forms import CommentForm
from django.views.generic import CreateView
from django.urls import reverse_lazy
class CommentCreate(CreateView):
model = Comment
form_class = CommentForm
template_name = 'crud_app/comment_create.html'
success_url = reverse_lazy('read_list')
このように、クラスベースビューの場合、処理を(ほぼ)実装することなく Create 操作を実現することが可能です。これは、クラスベースビューを定義する時に継承するスーパークラス側で、操作に必要な処理が既に実装されているからになります。これは、Create だけでなく、今後説明する Read・Update・Delete においても同様となります。
こういったクラスベースビューに関することは、前回の連載である下記ページで解説していますので、再度クラスベースビューについて学びたいという方は下記ページを参照していただければと思います。
【Django入門15】クラスベースビューの基本また、上記で継承を行った CreateView
に特化した解説ページも下記に用意していますので、CreateView
について詳しく知りたい方は下記ページをご参照ください。
R (Read) の実現
続いて、CRUD の R (Read) の実現方法について解説していきます。
Read とは、テーブルのレコードの参照です。言い換えると、Read とは、テーブルからのレコードの取得です。Read は、レコードの情報を表示する時に実施することの多い操作となります。
この Read には、大きく分けると2種類の Read が存在します。1つ目が「1つのレコードのみ」の Read で、2つ目が「複数のレコード」の Read となります。前者に関しては、1つのレコードの詳細(各種フィールドの値など)を表示する時に実施されることが多く、後者に関しては、複数のレコードのリスト(一覧)を表示する時に実施されることが多いです
1つのレコードに対する Read のデータベース操作
CRUD の Read は、モデルクラスのマネージャー objects
にメソッドを実行させることで実現することになります。
1つのレコードに対する Read は、下記のように objects
に get
メソッドを実行させることで実現できます。
instance = モデルクラス.objects.get(条件)
get
の引数には、取得したいレコードの条件をキーワード引数で指定します。この条件としては、「プライマリーキー (pk
)」のフィールドの条件を指定することが多いです。プライマリーキーとは、テーブル内のレコードを特定する識別子のようなフィールドで、このフィールドの値はテーブル内のレコード間で重複することはありません。したがって、プライマリーキーを指定してレコードの取得を行えば、必ず1つのレコードのみが取得できることになります(そのプライマリーキーを持つレコードが存在しなければ、例外が発生します)。
例えば、関数の引数で pk
を指定できるようにしておけば、関数内で下記のように get
メソッドを実行することで、引数で指定された pk
と同じプライマリーキーを持つレコードが取得できることになります。
instance = モデルクラス.objects.get(pk=pk)
また、同様のことを下記のように get_object_or_404
関数を利用して実現することも可能です。この場合、条件
を満たすレコードが存在しない場合、ステータスコード 404
のレスポンスが返却されるようになります。
instance = get_object_or_404(モデルクラス, 条件)
複数のレコードに対する Read のデータベース操作
また、複数のレコード対する Read に関しても、モデルクラスのマネージャー objects
にメソッドを実行させることで実現することができます。
例えば、テーブルに存在する全てのレコードの取得は、objects
に all
メソッドを実行させることで実現できます。
instances = モデルクラス.objects.all()
また、特定の条件を満たす全レコードの取得は、objects
に filter
メソッドを実行させることで実現できます。
instances = モデルクラス.objects.filter(条件)
filter
の引数には、取得したいレコードの各種フィールドに対する条件をキーワード引数で指定します。複数の条件が指定可能です。例えば下記のように filter
メソッドを実行させれば、User
に対応するテーブルから name
フィールドが 'Taro'
で age
フィールドが 30
であるレコードを全て取得することができます。
user_list = User.objects.filter(name='Taro', age=30)
Read のビュー
Read 操作の場合は、Create の時とは異なり、フォーム等の表示は不要です。
そのため、Read に対するリクエストを受け取った時には、ビューは前述の方法でレコードを取得し、取得したレコードを利用して何らかの処理を実行すれば良いだけになります。基本的には、render
関数の実行によって取得したレコードをテンプレートファイルに埋め込み、ページにレコードの各種フィールドの値を表示することが多いと思います。
ただ、特に「1つのレコードに対する Read 操作」をユーザーに提供する場合は、何らかの方法で取得するレコードをユーザーが指定できるようにしてやる必要があります。例えば、URL パターンの仕組みを利用し、URLでプライマリーキーを指定できるようにしてやれば、ユーザーが表示したいレコードの条件を URL で指定することができるようになります。
URL パターンに関しては、下記ページのビューの解説の中で説明していますので、詳細を知りたい方は下記ページを参照してください。
【Django入門3】ビュー(View)の基本とURLのマッピング関数ベースビューでの Read の例
上記での解説を踏まえて作成した Read 操作を行う関数ベースビュー comment_read_detail
と comment_read_list
の例は下記のようになります。comment_read_detail
が1つのレコードのみに対する Read を行い、comment_read_list
が複数のレコードに対する Read を行うようになっています。
from django.shortcuts import render, get_object_or_404
from .models import Comment
def comment_read_detail(request, pk):
# 引数pkをpkとするレコードを取得
comment = get_object_or_404(Comment, pk=pk)
context = {
'comment': comment
}
return render(request, 'crud_app/comment_read_detail.html', context)
from django.shortcuts import render
from .models import Comment
def comment_read_list(request):
# 全レコードを取得
comment_list = Comment.objects.all()
context = {
'comment_list': comment_list
}
return render(request, 'crud_app/comment_read_list.html', context)
上記の render
関数で利用している comment_read_detail.html
と comment_read_list.html
の例も下記に示しておきます(ヘッダーなどは省略しています)。
{{ comment.id }} : {{ comment.text }}
<table>
<tr>
<th>ID</th><th>本文</th>
</tr>
{% for comment in comment_list %}
<tr>
<td>{{ comment.id }}</td>
<td><{{ comment.text|truncatechars:10 }}</td>
</tr>
{% endfor %}
</table>
特に、複数のレコードの表示においては、上記の comment_read_list.html
のように、テンプレートタグを利用して for
ループを実装する必要があるという点が、テンプレートファイル作成時のポイントになると思います。
クラスベースビューでの Read の例
次は、Read 操作を行うクラスベースビューの例を示していきます。
Django では、1つのレコードに対する Read 操作を行うクラスベースビューとして DetailView
が、複数のレコードに対する Read 操作を行うクラスベースビューとして ListView
が定義されています。そのため、Create の時と同様に、これらを継承するクラスを定義し、さらにクラス変数の定義によるカスタマイズを行うだけで、Read 操作を行うビューが実現できます。
先ほど示した関数ベースビューの comment_read_detail
および comment_read_list
と同等の機能を持つクラスベースビュー CommentReadDetail
と CommentReadList
は下記の定義で実現できます。テンプレートファイルは、先ほど示した comment_read_detail.html
と comment_read_list.html
をそのまま利用するようにしています。
from .models import Comment
from django.views.generic import DetailView
class CommentReadDetail(DetailView):
model = Comment
template_name = 'crud_app/comment_read_detail.html'
from .models import Comment
from django.views.generic import ListView
class CommentReadList(ListView):
model = Comment
template_name = 'crud_app/comment_read_list.html'
上記で継承を行った DetailView
に関しては下記ページで、
ListView
に関しては下記ページで解説していますので、詳細を知りたい方はこれらのページを参照していただければと思います。
U (Update) の実現
次に、CRUD の U (Update) の実現方法について解説していきます。
Update とは、テーブルに既に存在するレコードの編集・更新となります。より具体的には、既に存在するレコードを、編集・更新後のレコードで上書き保存するが Update となります。
Update のデータベース操作
まずは、Update を実行するデータベース操作について説明します。
この Update は、基本的には次の3つの手順で実現することができます。
- Update 対象のテーブルからレコードを取得する
- 取得したレコードの各種フィールドを変更する(変更したいフィールドのみ)
- 変更後のレコードをテーブルに保存する
1. に関しては、先ほど説明した「1つのレコードに対する Read」によって実現可能です。この Read によって、テーブルのレコードがインスタンスとして取得できるため、そのインスタンスの各種フィールド(データ属性)の値を変更することで、2. が実現できることになります。さらに、3. に関しては、Create 同様にインスタンスに save
メソッドを実行させることで実現できます。
なので、Update は基本的に、ここまで解説してきた内容を利用して実現することが可能です。
例えば、プライマリーキーの値が pk
であるレコードを更新するのであれば、下記のような処理を実行すれば良いことになります。
instance = モデルクラス.objects.get(pk=pk)
instance.フィールド1 = 更新後のフィールドの値
instance.フィールド2 = 更新後のフィールドの値
instance.save()
Create や Update に対するデータベース操作のポイントの1つは、上記でも実行している save
メソッドになります。単にモデルクラスのコンストラクタで生成したインスタンスに save
メソッドを実行させた場合は、レコードの新規登録、すなわち Create 操作が行われることになります。それに対し、上記のようにテーブルから取得したインスタンス(レコード)に save
メソッドを実行させた場合は、レコードの更新、すなわち Update 操作が行われることになります。
このポイントを理解しておけば、Create 操作と Update 操作をどのように実現すればよいかのイメージが湧きやすくなると思います。
また、Create 同様に、ユーザーに Update 操作を提供する場合は、ユーザーにレコードの各種フィールドにセットする値をフォームで指定してもらうことが多いです。この場合、フォームから送信されてきたデータの各種フィールドの値を、上記の 更新後のフィールドの値
部分に指定してレコードの更新を行うことになります。
このフォームを「モデルフォームクラス」で実現している場合は、下記のような処理によっても Update を実施することが可能です。下記における 受信データ
とは、フォームから送信されてきたデータのことになります。
instance = モデルクラス.objects.get(pk=pk)
form = モデルフォームクラス(受信データ, instance=instance)
if form.is_valid():
form.save()
上記の処理の1行目によってレコードの取得が行われ、さらに2行目の処理で、取得したレコードの各種フィールドの値が、受信データ
の各種フィールドの値で上書きされた状態のフォームが生成されることになります。
さらに、そのフォームに save
を実行させれば、そのフォームの各種フィールドの値と同じフィールドの値を持つレコードで、テーブルから取得したレコード
の取得元のレコードが上書き保存され、Update 操作が行われることになります。
Update のビュー
Update 自体は、上記で紹介したいずれかの方法で実現可能です。ただし、ユーザーから Update 操作を実施できるようにするのであれば、ユーザーから Update 対象のレコードの指定と、そのレコードの更新後の各種フィールドの値の指定とが必要となります。
前者に関しては、Read のビュー でも紹介したように、URL パターンの仕組みを利用して URL で Update 対象のレコードを指定できるようにしてやれば良いです。
また、後者に関しては、Create の時と同様に、フォームの表示を行ってユーザーが各種フィールドの値を指定できるようにしてやれば良いです。ただし、各種フィールドが空のフォームを表示してしまうと更新前のフィールドの値が分からなくなってしまうため、更新前のフィールドの値をセットした状態のフォームを表示するようにした方が良いです。
フォームをモデルフォームクラスで実現している場合であれば、下記を実行することで、更新前のフィールドの値がセットされた状態のフォームを作成することができます。
form = モデルフォームクラス(instance=テーブルから取得したレコード)
以上を踏まえると、Update を実現するビューでは、まずメソッドが GET
のリクエストを受け取った時には、ユーザーから指定されたレコードの取得を行い、そのレコードから更新前のフィールドの値をセットした状態のフォームの表示を行う必要があります。さらに、メソッドが POST
のリクエストを受け取った時には、更新前のレコードを受信したデータの各種フィールドの値で更新し、さらに更新後のレコードをテーブルに保存する処理が必要となります。
関数ベースビューでの Update の例
上記での解説を踏まえて作成した、Update 操作を行う関数ベースビュー comment_update
の例は下記のようになります。
from django.shortcuts import render, redirect, get_object_or_404
from .forms import CommentForm
def comment_update(request, pk):
# 引数pkをpkとするレコードを取得
comment = get_object_or_404(Comment, pk=pk)
if request.method == 'POST':
# レコードのフィールドの値を送信されてきた値で上書きしたフォームを生成
form = CommentForm(request.POST, instance=comment)
if form.is_valid():
# 送信されてきたデータが妥当である場合
# 送信されてきたフィールドの値をセットしたレコードを保存
comment = form.save()
return redirect('read_list')
else:
# 更新前のレコードをのフィールドをセットしたフォームを生成
form = CommentForm(instance=comment)
context = {
'form': form,
'comment': comment
}
return render(request, 'crud_app/comment_update.html', context)
基本的な流れは、関数ベースビューでの Create の例 で示したものと同様となります。ただし、関数の引数で Update 対象のレコードの指定を受け付けるようにし、そのレコードの取得を行うという点、さらに、フォームを生成する際には、その取得したレコードを CommentForm
の instance
引数に指定するという点が異なります。逆に言えば、それ以外は Create 操作と Update 操作の実現方法は同様の処理で実現可能です。
上記の render
関数で利用している comment_update.html
の例も下記に示しておきます(ヘッダーなどは省略しています)。
<form action="{% url 'update' comment.pk %}" method="post">
{% csrf_token %}
<table>
{{ form }}
</table>
<p><input type="submit" value="送信"></p>
</form>
前述で示した urls.py
での定義からも分かるように、comment_update
の実行を要求するリクエストの URL では、Update 対象となるレコードの pk
を指定する必要があります。 そのため、フォームのボタンがクリックされた際に送信されるリクエストの URL に Update 対象となるレコードの pk
が指定されるよう、form
タグの action
属性を "{% url 'update' comment.pk %}"
としています。
クラスベースビューでの Update の例
次は、Update 操作を行うクラスベースビューについて解説します。
Update 操作も、ここまで示してきた Create 操作や Read 操作同様に、Django に定義されている UpdateView
を継承するクラスを定義し、さらにクラス変数の定義によって、そのクラスをカスタマイズすることで実現できます。
具体的には、先ほど示した関数ベースビューの comment_update
と同等の機能を持つクラスベースビュー CommentUpdate
は、下記の定義で実現できます。テンプレートファイルは、先ほど示した comment_update.html
をそのまま利用するようにしています。
from .models import Comment
from .forms import CommentForm
from django.views.generic import UpdateView
from django.urls import reverse_lazy
class CommentUpdate(UpdateView):
model = Comment
form_class = CommentForm
template_name = 'crud_app/comment_update.html'
success_url = reverse_lazy('read_list')
上記で継承を行った UpdateView
の詳細に関しては下記ページで解説していますので、UpdateView
について詳しく知りたい方は下記ページをご参照ください。
スポンサーリンク
D (Delete) の実現
次に、CRUD の D (Delete) の実現方法について解説していきます。
Delete とはテーブルに既に存在するレコードを削除する操作となります。
Delete のデータベース操作
まずは、Delete を実行するデータベース操作について説明します。
この Delete は、基本的には次の2つの手順で実現することができます。
- Delete 対象のテーブルからレコードを取得する
- 取得したレコードを削除する
1. に関しては、前述で説明した「1つのレコードに対する Read」によって実現可能です。さらに、2. に関しては、1. で取得したインスタンス(レコード)に delete
メソッドを実行させることで実現できます。
例えば、プライマリーキーの値が pk
であるレコードを削除するのであれば、下記のような処理を実行すれば良いことになります。
instance = モデルクラス.objects.get(pk=pk)
instance.delete()
Delete のビュー
Delete の場合、ユーザーからフィールドの値等を指定してもらう必要はないため、フォームを表示することは必須ではありません。ただし、Delete 対象のレコードをユーザーが指定できるよう、Read のビュー でも紹介したように、URL パターンの仕組みを利用して URL で Delete 対象のレコードを指定できるようにしてやる必要があります。
また、Delete 操作を行うビューにおいては、メソッドが GET
のリクエストを受け取った時には削除の確認用のページを表示し、メソッドが POST
のリクエストを受け取った時に、初めて実際のレコードの削除を実行するように作ることが多いです。
さらに、削除の確認用のページには、削除実行用のボタンを設置し、このボタンがクリックされたときにメソッドが POST
のリクエストが送信されるようにすることが多いです。
一般的なアプリにおいても、何かのデータを削除する時には「本当に削除してよいか?」等の削除の確認メッセージが表示されることが多いと思います。こういった動作を Django で開発するアプリを実現するために、上記のような動作のビューを作ることが多いです。また、削除の確認用のページには、削除対象のレコードの情報を表示してあげると、より使い勝手の良いアプリになると思います。
関数ベースビューでの Delete の例
上記での解説を踏まえて作成した、Delete 操作を行う関数ベースビュー comment_delete
の例は下記のようになります。
先ほど説明したように、メソッドが POST
の時に Delete 操作を行い、GET
の時には削除の確認用のページの表示を行うという点がポイントになります。
from django.shortcuts import render, redirect, get_object_or_404
from .forms import CommentForm
def comment_delete(request, pk):
# 引数pkをpkとするレコードを取得
comment = get_object_or_404(Comment, pk=pk)
if request.method == 'POST':
# レコードを削除
comment.delete()
return redirect('read_list')
context = {
'object': comment,
}
return render(request, 'crud_app/comment_delete.html', context)
上記の render
関数で利用している comment_delete.html
の例も下記に示しておきます(ヘッダーなどは省略しています)。
<p>下記のレコードを削除してよろしいですか?</p>
<table>
<tr>
<td>ID</td><td>{{ comment.id }}</td>
</tr>
<tr>
<td>本文</td><td>{{ comment.text }}</td>
</tr>
</table>
<form action="{% url 'delete' comment.pk %}" method="post">
{% csrf_token %}
<p><input type="submit" value="削除"></p>
</form>
単に削除の確認のメッセージを表示するだけでなく、削除対象のレコードの各種フィールドの値も表示するようにしています。また、削除 OK とユーザーが判断したときに、削除操作をユーザーが指示できるよう、削除
ボタンを表示するようにしています。この 削除
ボタンがクリックされた時には、form
タグの action
属性と method
属性により、メソッドが POST
かつ、Delete 操作を要求する URL のリクエストが送信されるようにしています。そのため、この 削除
ボタンのクリックによって実際の Delete 操作が実施されることになります。
また、Update 操作のとき同様に、ボタンクリック時に送信されるリクエストの URL には Delete 対象となるレコードが指定されるようにフォーム・ボタンを作成する必要があるという点もポイントとなります。
クラスベースビューでの Delete の例
次は、Delete 操作を行うクラスベースビューについて解説します。
Delete 操作も、ここまで示してきた他の操作同様に、Django に定義されている DeleteView
を継承するクラスを定義し、さらにクラス変数の定義によって、そのクラスをカスタマイズすることで実現できます。
具体的には、先ほど示した関数ベースビューの comment_delete
と同等の機能を持つクラスベースビュー CommentDelete
は、下記の定義で実現できます。テンプレートファイルは、先ほど示した comment_delete.html
をそのまま利用するようにしています。
from .models import Comment
from django.views.generic import DeleteView
from django.urls import reverse_lazy
class CommentDelete(DeleteView):
model = Comment
template_name = 'crud_app/comment_delete.html'
success_url = reverse_lazy('read_list')
上記で継承を行った DeleteView
の詳細に関しては下記ページで解説していますので、UpdateView
について詳しく知りたい方は下記ページをご参照ください。
CRUD 操作の利用制限の実現
続いて、CRUD 操作の利用制限の実現方法について解説していきます。
ここまで説明してきた内容に基づいてビューやテンプレートファイルの作成を行えば、CRUD の各操作が実現できると思います。
ただし、ここまで説明してきた内容のみに基づいて CRUD 操作を実現した場合、誰でも・どんなレコードに対しても CRUD 操作が実施できるようになってしまいます。このような状態だと、例えば、あなたが投稿したコメントを他のユーザーが勝手に削除したり編集したりすることも可能で、悪意あるユーザーによって、あなたが投稿したコメントが他者を誹謗中傷するようなものに書き換えられてしまう可能性もあります。
こういったことが行われるとトラブルに発展しかねませんし、ウェブアプリの評判も低下してしまいます。そのため、こういったことが行われないよう、特に Update 操作と Delete 操作に関しては、実施できるユーザーを制限するようにした方が良いです。
例えば、投稿コメントに対する Update 操作と Delete 操作を、投稿したユーザー本人及びウェブアプリの運営者(スーパーユーザー)しか実施できないようにしてあげると、上記のような他のユーザーからの更新や削除を防ぐことができるようになります。これだと安心してアプリが利用できますね!
特に、Update 操作や Delete 操作をユーザーが実施可能なウェブアプリを開発する際には、それらの操作をユーザーに応じて利用制限する(拒否)することが重要となります。
CRUD 操作の利用制限の実現方法
このような操作の制限は、下記ページで解説した「ログイン機能」をウェブアプリに搭載することで簡単に実現できるようになります。
【Django入門10】ログイン機能の実現ログイン機能をウェブアプリに搭載すれば、リクエストを送信してきたユーザーがログイン中の場合、リクエストを送信してきたユーザーをビューの関数内等で特定できるようになります。
なので、リクエスト送信者に応じて各種操作の制限をかけるようなことが可能となります。例えば、リクエスト送信者が管理者権限を持っているのであれば Update 操作や Delete 操作を許可し、それ以外は拒否するような制御を実施することができます。また、リクエスト送信者がレコードの作成者の場合のみ、そのレコードに対する Update 操作や Delete 操作を許可し、それ以外は拒否するようなことも可能です。
こんな感じで、特定のレコードに対する操作を許可するか拒否するかをリクエスト送信者に応じて切り替えるようにすることで、意図しないユーザーからの CRUD 操作を制限することができるようになります。
関数ベースビューでの Delete 操作の制限
次は、上記のような CRUD 操作への利用制限のかけ方を、実際のコードを示して説明していきたいと思います。掲示板アプリのコメントに対する Delete 操作を例に挙げて解説していきますが、Update 操作に関しても同様の方法で利用制限することが可能です。
ここでは、models.py
に下記の Comment
が定義されていることを前提に解説を進めます。下記における User
は、Django で定義されているユーザー管理モデルクラスとなります。
from django.db import models
from django.contrib.auth.models import User
class Comment(models.Model):
text = models.CharField(max_length=256)
user = models.ForeignKey(CustomUser, on_delete=models.CASCADE, null=True)
上記のように Comment
を定義しておけば、user
フィールドで、その Comment
のレコードの作成者、すなわち、そのコメントの投稿者を管理することができるようになります。
具体的には、ログイン機能を搭載し、Comment
のレコードを作成するタイミングで user
フィールドに「レコード作成のリクエストを送信してきたユーザー」をセットするようにすれば、そのコメントの投稿者を管理することが可能となります(レコード作成時のコードの紹介は省略しますので、その点はご了承ください)。
上記のように Comment
を定義し、さらに user
フィールドでレコードの作成者を管理するようにしている場合、下記のように Delete 操作を行うビューの関数を定義すれば、Delete 対象のレコードの投稿者がリクエスト送信者と一致しない場合に例外が発生し、Delete 操作が実施できないようになります。
from django.shortcuts import render, redirect, get_object_or_404
from .forms import CommentForm
from django.core.exceptions import PermissionDenied
def comment_delete(request, pk):
# 引数pkをpkとするレコードを取得
comment = get_object_or_404(Comment, pk=pk)
if comment.user != request.user:
# コメントの投稿者がリクエスト送信者でない場合はDelete操作を実施しない
raise PermissionDenied()
if request.method == 'POST':
# レコードを削除
comment.delete()
return redirect('read_list')
context = {
'object': comment,
}
return render(request, 'crud_app/comment_delete.html', context)
上記の処理からも分かるように、ログイン機能を搭載した場合、request.user
がリクエスト送信者(User
のインスタンス)を参照するようになるため、単純に comment.user != request.user
の比較によってコメントの投稿者とリクエスト送信者が一致するかどうかを判断することができるようになります。
一致しない場合にエラーのレスポンスを return
で返却したり、例外を発生させるようにすれば、そのタイミングで関数が終了することになり、実際の各種操作が実行されなくなります。例外を発生させる場合は、上記のように例外 PermissionDenied
を発生させると良いと思います。この例外が発生した際には、クライアントに対して 403
エラーのレスポンスが返却されることになります。
また、上記は投稿者以外のコメントの削除を制限する例となりますが、投稿者でなくても、スーパーユーザー等の特別なユーザーに関しては全レコードの削除を許可したいような場合もあります。この場合も、結局は上記のようにビューの関数内でユーザーの権限を調べ、その権限に応じて Delete 操作を行うか例外を発生させるかを切り替えるようにしてやれば良いだけです。
例えば、投稿者だけでなく、スーパーユーザーに関してもレコードの削除を許可するようにしたい場合は、上記の comment_delete
を次のように変更してやれば良いです。このように、操作の制限のかけ方に関しては、条件の追加によって自由自在に変更することが可能です。
from django.shortcuts import render, redirect, get_object_or_404
from .forms import CommentForm
from django.core.exceptions import PermissionDenied
def comment_delete(request, pk):
# 引数pkをpkとするレコードを取得
comment = get_object_or_404(Comment, pk=pk)
if not request.user.is_superuser:
if comment.user != request.user:
# コメントの投稿者がリクエスト送信者でない場合はDelete操作を実施しない
raise PermissionDenied()
if request.method == 'POST':
# レコードを削除
comment.delete()
return redirect('read_list')
context = {
'object': comment,
}
return render(request, 'crud_app/comment_delete.html', context)
クラスベースビューでの Delete 操作の制限
次は、クラスベースビューでの Delete 操作の制限例を示していきます。
考え方に関しては、これまで説明してきた通りで、リクエスト送信者に応じて Delete を実施するか例外を発生させるかを切り替えるようにしてやれば良いです。
ただし、クラスベースビューの場合、基本的には処理の実装を行わないため、関数ベースビューの時のように、単に関数内に上記のような処理を追加すればよいというわけではありません。
クラスベースビューの場合は、メソッドのオーバーライドや Mixin
の継承によって、各種操作の制限を行うことになります。
まず、メソッドのオーバーライドでの操作の制限は、下記のように Delete 操作を行う CommentDelete
を定義することで実現できます。
from .models import Comment
from django.views.generic import DeleteView
from django.urls import reverse_lazy
from django.core.exceptions import PermissionDenied
class CommentDelete(DeleteView):
model = Comment
template_name = 'crud_app/comment_delete.html'
success_url = reverse_lazy('read_list')
def get_object(self, queryset=None):
comment = super().get_object(queryset)
if comment.user != self.request.user:
raise PermissionDenied()
return comment
この CommentDelete
は、クラスベースビューでの Delete の例 で示した CommentDelete
に get_object
メソッドの定義を追加してオーバーライドするようにしただけのものになります。
この CommentDelete
が継承している DeleteView
の get_object
メソッドは、削除対象のレコードを返却するメソッドになります。そのため、CommentDelete
に定義した get_object
メソッドの1行目が実行されれば、削除対象のレコードが取得できることになります。あとは、この取得したレコードの作成者、すなわちコメントの投稿者とリクエスト送信者が一致しない場合に例外を返却するようにすれば、関数ベースビューの時と同様に、コメント削除をコメントの投稿者以外からは実施できないようにすることができます。
また、Mixin
の継承によっても、操作の制限を行うことが可能です。その例が下記となります。
from .models import Comment
from django.views.generic import DeleteView
from django.urls import reverse_lazy
from django.core.exceptions import PermissionDenied
from django.contrib.auth.mixins import UserPassesTestMixin
class AuthorRequiredMixin(UserPassesTestMixin):
def test_func(self):
comment = self.get_object()
return comment.user == self.request.user
class CommentDelete(AuthorRequiredMixin, DeleteView):
model = Comment
template_name = 'crud_app/comment_delete.html'
success_url = reverse_lazy('read_list')
この CommentDelete
では、AuthorRequiredMixin
を継承するようにしています。
また、AuthorRequiredMixin
は UserPassesTestMixin
を継承しており、この UserPassesTestMixin
は、test_func
メソッドを実行し、このメソッドが True
を返却した場合にビューを実行し、それ以外は PermissionDenied
例外を発生させるような作りになっています。
そのため、AuthorRequiredMixin
で test_func
メソッドをオーバーライドし、コメントの投稿者とリクエスト送信者が一致する場合に True
を、それ以外の場合に False
を返却するようにすることで、コメントの投稿者とリクエスト送信者が一致しない場合に例外が発生して Delete 操作が実施されないようにすることができます。
こんな感じで、ちょっと難易度は上がりますが、クラスベースビューの場合でも各種操作に制限をかけることが可能です。CRUD 操作に関しては、単にこれらの操作を実現するだけでなく、適切に操作に制限をかけることも重要となりますので、上記のような操作の制限方法は是非覚えておきましょう!
スポンサーリンク
掲示板アプリで CRUD 操作を実現する
最後に、掲示板アプリへの CRUD 操作の追加例を示していきたいと思います。
この Django 入門 の連載の中では簡単な掲示板アプリを開発してきており、前回の連載では下記ページでクラスベースビューの導入を行いました。
【Django入門15】クラスベースビューの基本今回は、上記ページの 掲示板アプリのビューをクラスベースに変更する で示したアプリに CRUD 操作を追加していきたいと思います。この掲示板アプリでは、ユーザーを管理するモデルクラス CustomUser
とコメントを管理するモデルクラス Comment
の2つのモデルクラスを定義していますが、ここでは Comment
に対する CRUD 操作のみの追加を行っていきたいと思います。
また、既に Create 操作と Read 操作に関しては下記のクラスベースビューで実現済みですので、Update 操作と Delete 操作のみの追加を行っていきます。これらの操作もクラスベースビューで実装していきます。
Post
(Create 操作)CommentDetail
(1つのレコードの Read 操作)CommentList
(複数のレコードの Read 操作)
また、今回追加する Comment
に対する Update 操作と Delete 操作に関しては、そのコメントの投稿者のみが実施できるようにしていきたいと思います。
掲示板アプリのプロジェクト一式の公開先
この Django 入門 の連載を通して開発している掲示板アプリのプロジェクトは GitHub の下記レポジトリで公開しています。
https://github.com/da-eu/django-introduction
また、前述のとおり、ここでは前回の連載の 掲示板アプリのビューをクラスベースに変更する で作成したプロジェクトをベースに変更を加えていきます。このベースとなるプロジェクトは下記のリリースで公開していますので、必要に応じてこちらからプロジェクト一式を取得してください。
https://github.com/da-eu/django-introduction/releases/tag/django-classview
さらに、ここから説明していく内容の変更を加えたプロジェクトも下記のリリースで公開しています。以降では、基本的には前回からの差分のみのコードを紹介していくことになるため、変更後のソースコードの全体を見たいという方は、下記からプロジェクト一式を取得してください。
https://github.com/da-eu/django-introduction/releases/tag/django-crud
mixins.py
の追加
では、ソースコードの変更を行い、コメントに対する Update 操作と Delete 操作を追加していきたいと思います。
今回、コメントに対する Update 操作と Delete 操作を投稿者以外が実施できないようにするため、Update 操作と Delete 操作のクラスベースビューには CRUD 操作の利用制限の実現 でも紹介した AuthorRequiredMixin
を継承するようにしたいと思います。
そのためには、AuthorRequiredMixin
の定義が必要となりますので、まずは、その定義先のファイルとして forum
フォルダ内に mixins.py
を新規に追加してください。さらに、そのファイルの中身を下記のように変更して保存してください。
from django.contrib.auth.mixins import UserPassesTestMixin
class AuthorRequiredMixin(UserPassesTestMixin):
def test_func(self):
comment = self.get_object()
return comment.user == self.request.user
スポンサーリンク
views.py
の変更
続いて、views.py
に Update 操作用のクラスと Delete 操作用のクラスの定義を追加していきます。これらのクラス名は、それぞれ CommentUpdate
と CommentDelete
としたいと思います。
これらのクラスに、先ほど mixins.py
に定義した AuthorRequiredMixin
を継承させることで、コメントの Update 操作と Delete 操作を投稿者以外が実施しようとした場合に例外が発生し、クライアントには 403
エラーのレスポンスが返却されるようになります。
CommentUpdate
と CommentDelete
の定義を追加した views.py
は下記のようになります。
from .forms import RegisterForm, PostForm, LoginForm
from .models import Comment
from .mixins import AuthorRequiredMixin
from django.contrib.auth import login
from django.contrib.auth import get_user_model
from django.core.paginator import Paginator
from django.views.generic import ListView, DetailView, RedirectView, CreateView, UpdateView, DeleteView
from django.contrib.auth.views import LoginView, LogoutView
from django.contrib.auth.mixins import LoginRequiredMixin
from django.urls import reverse, reverse_lazy
User = get_user_model()
class Login(LoginView):
form_class = LoginForm
template_name = 'forum/login.html'
def get_success_url(self):
return reverse('user', kwargs={'user_id':self.request.user.id})
class Logout(LogoutView):
next_page = reverse_lazy('login')
class Index(RedirectView):
url = reverse_lazy('comments')
class UserList(LoginRequiredMixin, ListView):
queryset = User.objects.prefetch_related('comments').order_by('date_joined')
template_name = 'forum/users.html'
paginate_by = 3
page_kwarg = 'p'
class UserDetail(LoginRequiredMixin, DetailView):
model = User
pk_url_kwarg = 'user_id'
template_name = 'forum/user.html'
def get_context_data(self, **kwargs):
comments = Comment.objects.filter(user=self.object).order_by('date')
paginator = Paginator(comments, 3)
number = int(self.request.GET.get('p', 1))
page_obj = paginator.page(number)
return super().get_context_data(page_obj=page_obj)
class CommentList(LoginRequiredMixin, ListView):
queryset = Comment.objects.select_related('user').order_by('date')
paginate_by = 3
page_kwarg = 'p'
template_name = 'forum/comments.html'
class CommentDetail(LoginRequiredMixin, DetailView):
model = Comment
pk_url_kwarg = 'comment_id'
template_name = 'forum/comment.html'
class Register(CreateView):
model = User
form_class = RegisterForm
template_name = 'forum/register.html'
def form_valid(self, form):
response = super().form_valid(form)
user = self.object
login(self.request, user)
return response
def get_success_url(self):
return reverse('user', kwargs={'user_id':self.object.pk})
class Post(LoginRequiredMixin, CreateView):
form_class = PostForm
template_name = 'forum/post.html'
success_url = reverse_lazy('comments')
def form_valid(self, form):
form.instance.user = self.request.user
return super().form_valid(form)
class CommentUpdate(LoginRequiredMixin, AuthorRequiredMixin, UpdateView):
model = Comment
form_class = PostForm
pk_url_kwarg = 'comment_id'
template_name = 'forum/update.html'
success_url = reverse_lazy('comments')
class CommentDelete(LoginRequiredMixin, AuthorRequiredMixin, DeleteView):
model = Comment
pk_url_kwarg = 'comment_id'
template_name = 'forum/delete.html'
success_url = reverse_lazy('comments')
CommentUpdate
と CommentDelete
には LoginRequiredMixin
の継承も必要で、これにより非ログインユーザーからの Update 操作と Delete 操作を拒否することができるようになっています(強制的に他のページにリダイレクトされる)。
あとは、先ほど定義した AuthorRequiredMixin
を継承させることで、投稿者以外からの操作も拒否されるようにしています。
CommentUpdate
と CommentDelete
で定義しているクラス変数の詳細に関しては、前回の連載の下記ページや、
下記の UpdateView
や DeleteView
の解説ページで説明していますので、詳細を知りたい方は、これらのページを参照してください。
urls.py
の変更
新たに Update 操作用のビューと Delete 操作用のビューを追加したため、次は、これらの操作を要求するための URL の定義、および、その URL と追加したビューとのマッピングを行います。
これは、下記のように forum
フォルダ内の urls.py
を変更することで実現できます。変更点は、urlpatterns
の末尾側の2つの要素の追加のみです。
from django.urls import path
from . import views
urlpatterns = [
path('', views.Index.as_view(), name='index'),
path('comments/', views.CommentList.as_view(), name='comments'),
path('comment/<int:comment_id>', views.CommentDetail.as_view(), name='comment'),
path('users/', views.UserList.as_view(), name='users'),
path('user/<int:user_id>', views.UserDetail.as_view(), name='user'),
path('register/', views.Register.as_view(), name='register'),
path('post/', views.Post.as_view(), name='post'),
path('login/', views.Login.as_view(), name='login'),
path('logout/', views.Logout.as_view(), name='logout'),
path('update/<int:comment_id>/', views.CommentUpdate.as_view(), name='update'),
path('delete/<int:comment_id>/', views.CommentDelete.as_view(), name='delete'),
]
urlpatterns
の後ろから2番目の要素の追加により、ウェブアプリが下記の形式の URL のリクエストを受け付けるようになります。{comment_id}
には Update したいレコードのプライマリーキーを整数で指定します。この URL のリクエストを受け取った際には、views.py
に定義した CommentUpdate
が動作し、{comment_id}
と同じ値をプライマリーキーとするレコードに対して Update 操作が実施されることになります。
/forum/update/{comment_id}
同様に、urlpatterns
の最後の要素の追加により、ウェブアプリが下記の形式の URL のリクエストを受け付けるようになります。{comment_id}
には Delete したいレコードのプライマリーキーを整数で指定します。この URL のリクエストを受け取った際には、views.py
に定義した CommentDelete
が動作し、{comment_id}
と同じ値をプライマリーキーとするレコードに対して Delete 操作が実施されることになります。
/forum/delete/{comment_id}
テンプレートファイルの追加・変更
続いて、先ほど定義したクラスから利用されるテンプレートファイル update.html
と delete.html
の追加を行っていきます。さらに、Update 操作や Delete 操作のリクエストをリンクのクリックで送信できるようにするため、comment.html
(コメントの詳細を表示するページのテンプレートファイル) にリンクの追加を行っていきます。
update.html
の追加
まずは、Update 操作用のテンプレートファイル、より具体的には、コメントの編集フォームを表示するテンプレートファイルの追加から行っていきます。
forum/templates/forum
フォルダの下に update.html
を新規に作成し、下記のように中身を変更して保存してください。
{% extends "forum/base.html" %}
{% block title %}
コメントの編集
{% endblock %}
{% block main %}
<h1>コメントの編集</h1>
<form action="{% url 'update' comment.id %}" method="post">
{% csrf_token %}
<table class="table table-hover">{{ form.as_table }}</table>
<p><input type="submit" class="btn btn-primary" value="送信"></p>
</form>
{% endblock %}
基本的に、form
と 送信
ボタンを表示するだけのテンプレートファイルになっています。CRUD 操作の実現 で示した Update 操作用のテンプレートファイルでは action
属性を "{% url 'update' comment.pk %}"
としており、それに対して上記では comment.pk
部分を comment.id
に変更していますが、モデルクラス Comment
においては、これらは両方ともプライマリーキーのフィールドになっているため、上記においても 送信
ボタンクリック時に Update 対象のプライマリーキーを指定する URL のリクエストが送信されることになります。
delete.html
の追加
次は、Delete 操作用のテンプレートファイル、より具体的には、コメントの削除の確認ページを表示するテンプレートファイルの追加を行います。
forum/templates/forum
フォルダの下に delete.html
を新規に作成し、下記のように中身を変更して保存してください。
{% extends "forum/base.html" %}
{% block title %}
コメントの削除
{% endblock %}
{% block main %}
<h1>コメントの削除</h1>
<p>下記のコメントを削除してもよろしいですか?</p>
<table class="table table-hover">
{% if comment.user is not None %}
<tr><td>投稿者</td><td>{{ comment.user.username }}</td></tr>
{% else %}
<tr><td>投稿者</td><td>不明</td></tr>
{% endif %}
<tr><td>本文</td><td>{{ comment.text }}</td></tr>
<tr><td>投稿日</td><td>{{ comment.date|date }}</td></tr>
</table>
<form action="{% url 'delete' comment.id %}" method="post">
{% csrf_token %}
<p><input type="submit" class="btn btn-primary" value="削除"></p>
</form>
{% endblock %}
上記は、コメントの削除の確認を行うページのテンプレートファイルになっており、削除対象のコメントの情報の表示、及び、削除
ボタンの表示を行っています。
comment.html
の変更
最後に、comment.html
の変更を行っていきます。
この comment.html
はコメントの詳細を示すテンプレートファイルになっています。このテンプレートファイルに 編集
と 削除
のリンクを追加し、クリックによって、表示中のコメントの Update 操作と Delete 操作が実施できるようにしたいと思います。
そのため、comment.html
を下記のように変更します。表に行を1つ追加し、そこに 編集
リンクと 削除
リンクを表示するようにしています。
{% extends "forum/base.html" %}
{% block title %}
コメント
{% endblock %}
{% block main %}
<h1>コメント({{ comment.id }})</h1>
<table class="table table-hover">
{% if comment.user is not None %}
<tr><td>投稿者</td><td><a href="{% url 'user' comment.user.id %}">{{ comment.user.username }}</a></td></tr>
{% else %}
<tr><td>投稿者</td><td>不明</td></tr>
{% endif %}
<tr><td>本文</td><td>{{ comment.text }}</td></tr>
<tr><td>投稿日</td><td>{{ comment.date|date }}</td></tr>
<tr>
<td class="text-center"><a href="{% url 'update' comment.id %}">編集</a></td>
<td class="text-center"><a href="{% url 'delete' comment.id %}">削除</a></td>
</tr>
</table>
{% endblock %}
そもそも、今回は Update や Delete は投稿者のみに許可される操作となるため、コメントの投稿者以外には 編集
リンクと 削除
リンクを非表示にするというのもアリです。むしろ、そちらの方がアプリとしては使いやすいと思います。
ただ、後半の動作確認での「投稿者以外による Update 操作や Delete 操作が実施不可であることの確認」をやりやすくするため、あえて 編集
リンクと 削除
リンクを全ユーザーに表示するようにしています。
参考のため、投稿者以外には 編集
リンクと 削除
リンクを非表示にするようにした comment.html
も下記に示しておきます。先ほど示した.comment.html
に対して {% if comment.user == user %}
と {% endif %}
の追加のみを行っています。このテンプレートファイルは、今後の動作確認時には利用しないので、その点だけは注意してください。
{% extends "forum/base.html" %}
{% block title %}
コメント
{% endblock %}
{% block main %}
<h1>コメント({{ comment.id }})</h1>
<table class="table table-hover">
{% if comment.user is not None %}
<tr><td>投稿者</td><td><a href="{% url 'user' comment.user.id %}">{{ comment.user.username }}</a></td></tr>
{% else %}
<tr><td>投稿者</td><td>不明</td></tr>
{% endif %}
<tr><td>本文</td><td>{{ comment.text }}</td></tr>
<tr><td>投稿日</td><td>{{ comment.date|date }}</td></tr>
{% if comment.user == user %}
<tr>
<td class="text-center"><a href="{% url 'update' comment.id %}">編集</a></td>
<td class="text-center"><a href="{% url 'delete' comment.id %}">削除</a></td>
</tr>
{% endif %}
</table>
{% endblock %}
以上で、コードの変更は完了となります。
スポンサーリンク
動作確認
最後に動作確認を行なっていきましょう!
ここでは、自身で投稿したコメントの Update 操作と Delete 操作が実施できること、さらに自身で投稿したコメント以外の Update 操作と Delete 操作が実施不可であることを確認していきたいと思います。
マイグレーションの実行
まず、マイグレーションを実行していきたいと思います。
いつも通り、testproject
フォルダの中、つまり manage.py
が存在するフォルダで下記コマンドを実行してください。
% python manage.py makemigrations
% python manage.py migrate
開発用ウェブサーバーの起動
マイグレーションが完了した後は、いつも通り Django 開発用ウェブサーバーの起動を行います。マイグレーションを実行した時と同じフォルダで下記コマンドを実行すれば、Django 開発用ウェブサーバーが起動します。
% python manage.py runserver
ユーザーの登録とコメントの投稿
前準備として、ユーザーの登録とコメントの投稿を行っていきます。ここでは、2人のユーザーの登録、および、各ユーザーからのコメントの投稿を実施していきます。
まずはユーザーの登録から実施していきましょう!
ユーザー登録フォームは、開発用ウェブサーバーを起動した状態で下記 URL をウェブブラウザで開くことで表示することができます。
http://localhost:8000/forum/register/
上記 URL を開くと下の図のようなフォームが表示されますので、それぞれのフィールドに値を入力し、送信
ボタンをクリックしてユーザー登録を行ってください。
ユーザー登録を行うと自動的にログインされますので、続いてナビゲーションバーの コメント投稿
リンクをクリックし、表示されるフォームでコメントの投稿を行ってください。
コメント投稿後は、ナビゲーションバーの ログアウト
リンクをクリックしてログアウトを実施し、その後、先ほどと同様の手順でユーザーの登録、および、登録したユーザーからのコメントの投稿を実施してください。
これにより、2人のユーザーが登録され、さらに各ユーザーからコメントが1つずつ投稿されたことになります。これで、動作確認の前準備は完了です!
投稿者からの Update 操作と Delete 操作
ここから、今回の変更内容の動作確認、すなわち、今回追加した Update 操作と Delete 操作の動作確認を行っていきます。
まず、ナビゲーションバーの ログイン
リンクをクリックしてログインフォームを表示してください。そして、先ほど追加したユーザーのどちらかのユーザー名とパスワードを入力してログインを実施してください。
続いて、ナビゲーションバーの コメント一覧
リンクをクリックしてコメント一覧を表示してください。このコメント一覧では、コメントの本文をクリックすることで、そのコメントの詳細が表示できるようになっています。ここでは、まず、「現在ログイン中のユーザーが 投稿者
となっているコメント」の本文をクリックしてください。
さらに、表示されるコメントの詳細ページに設置されている 編集
リンクをクリックしてください。
すると、下の図のようなフォームが表示されるはずです。Text
フィールドには、先ほど表示したコメントの本文が入力されているはずです。このように、編集前のフィールドの値が表示されるという点が、編集フォーム(更新フォーム)のポイントの1つとなります。
ここで、その Text
フィールドに入力されている文字列を変更し、送信
ボタンをクリックしてください。
この 送信
ボタンのクリックにより、コメント一覧が再び表示されると思います。ここで注目していただきたいのが、先ほど表示したコメントの本文で、この本文が、先ほどフォームの Text
フィールドに入力した文字列に変更されているはずです。このように、フォームに入力された値に基づいてレコードが更新されていることから、コメントに対する Update 操作が実現できていることが確認できたことになります。
次は、先ほど Update 操作を行ったコメントの本文をクリックし、再度コメントの詳細を表示してください。そして、次は 削除
リンクをクリックしてください。これにより、下の図のようなページが表示されると思います。
ここには、削除しようとしているコメントの情報が表示されています。続いて、削除
ボタンをクリックしてください。これにより、表示していたコメントの削除が実行されます。削除
ボタンクリック後に表示されるコメント一覧にも、削除を実行したコメントが表示されなくなっていることが確認できるはずです。この動作より、Delete 操作も実現できていることが確認できたことになります。
投稿者以外からの Update 操作と Delete 操作
次は、コメント一覧から、「ログイン中のユーザーが投稿者となっていないコメント」の本文をクリックして、そのコメントの詳細ページを開いてください。続いて、編集
リンクをクリックしてみてください。編集
リンクをクリックをすると、下の図のようなページが表示され、エラーが発生していることが確認できると思います。
さらに、ウェブブラウザの戻るボタンをクリックして再度コメントの詳細ページを開き、今度は 削除
リンクをクリックしてみてください。この場合も、同様のページが表示されることが確認できると思います。
このようなページが表示されるのは、CommentUpdate
に継承させた AuthorRequiredMixin
で、例外 PermissionDenied
が発生したためとなります。例外が発生したのは、コメントの投稿者でないユーザーが Update 操作や Delete 操作を実施しようとしたからになります。
この動作より、投稿者以外のコメントに対する Update 操作と Delete 操作の利用が制限されていることが確認できたことになります。
ここまで説明してきたように、CRUD 操作をユーザーが実行できるようにする場合は、単に各種操作を実現するだけでなく、その操作の利用を制限することも重要なので、この点は是非覚えておいてください!
まとめ
このページでは、Django での CRUD 操作の実現方法について解説しました!
CRUD 操作とは、下記の4つの操作の頭文字を並べた用語になります。操作対象は、すなわちデータベースのテーブルのレコード(モデルクラスのインスタンス)となります。
- Create:レコードの登録
- Read:レコードの参照
- Update:レコードの更新
- Delete:レコードの削除
一般的なウェブアプリにおいても、この CRUD 操作がユーザーから利用できるようになっているものが多いです。Django でウェブアプリを開発する場合も、各種モデルクラスに対して各操作の必要性を考え、必要に応じて各操作を実施できるようにビューなどを作成するようにしましょう!
ただし、特に Update や Delete の操作を誰でも行えてしまうとトラブルが発生する可能性があるので、これらの操作は実施できるユーザーを制限するようにした方が良いです。また、必須でないなら、そもそも Update 操作や Delete 操作を実装しないというのも一つの手で、この方がアプリが簡単に作れますし、ウェブアプリの運営も楽になると思います。
とりあえず、今後もウェブアプリ開発に携わるのであれば、必ず CRUD という言葉は聞くと思いますし、いつかは各操作が必要となるウェブアプリ開発を行うことになると思いますので、CRUD の言葉の意味や各操作の実現方法については覚えておきましょう!