知識の枝

"All is well"

Djangoでモデルを更新・編集する方法

約474日前 2021年4月25日15:59
デジタル
Django

改訂履歴


2021/4/25 投稿

1. 背景


Djangoを使った開発中に覚えたことを備忘録として残します。

今回は作成済みのモデルをウェブページ上で更新・編集する方法を解説します。


2. ゴール


ウェブページ上でモデルを更新・編集する。


3. はじめに


まずこれから実施する更新・編集のイメージを共有します。



Todoアプリを作るとき、個別のタスクに下記のような5つの状況ステータスを設けると仮定します。
未着手、実施中、完了、保留、中止

models.pyではこのように定義します。
models.py
class Status(models.Model):
"""Status list"""
status = models.CharField(verbose_name='状況', max_length=10, blank=True, null=True)

def __str__(self):
return self.status

class Task(models.Model):
"""Task info"""
task = models.CharField(verbose_name='やること', max_length=255, blank=True, null=True)
status = models.ForeignKey(
Status, verbose_name='状況', on_delete=models.PROTECT)

def __str__(self):
return self.task
ステータスというモデルを作成しておき、タスクモデルでステータスの中からForeignKeyで1つ選択します。

5つのステータスは予めDjangoのadminページで作っておきます。



作成済みのタスクのステータスを変更する場合を考えましょう。

例えば「未着手」状態のタスクを開始し、「実施中」のステータスに変更する場合や、
もしくは「実施中」だったタスクが終わったので、「完了」ステータスに変更する場合があります。



このように
作成済みのモデルのパラメーターを変更する方法を次項で解説します。


4. モデルの更新・編集


4.1 - ワンクリック変更


上記のタスク管理イメージが「ワンクリック変更」に該当します。


「着手」ボタンを押せば、そのタスクのステータスが「未着手」→「実施中」に変わります。


html上の着手ボタンは下記のように記述し、ボタンが押されたタスクのidを参照し、専用のページに移動するようにします。
html
<a class="btn btn-outline-success" href="/taskapp/start/{{task.id}}" role="button">
着手
</a>
まだurls.pyに上記ページの設定を作っていないので、作ります。

urls.py
urlpatterns = [
path('', views.TaskView.as_view(), name='top_page'),
path('start/<int:pk>', views.Start, name='start'), #ここを追加
]
これでボタンが押されたときに「Start」というビューが実行される準備ができました。


次にviews.pyに実行するビューを作成します。
views.py
def Start(request, pk):
task = get_object_or_404(Task, pk=pk)
new_status = Status.objects.get(status="Ongoing")
task.status = new_status
task.save()
return redirect('/taskapp')
このビューの中にタスクのステータスを書き換える処理を書きます。

このビューが実行されるとき、一緒に「pk」という変数が渡されています。
def Start(request, pk):
この「pk」は何かというと、着手ボタンに割り当てられていたurlの{{task.id}}の部分になります。
href="/taskapp/start/{{task.id}}"


「task.id」は書き換え対象タスクのID(識別番号のようなもの)です。

この「task.id」が入ったurlがクリックされたことで、urls.pyの'start/<int:pk>'の<int:pk>部分がtask.idに置き換わります。
 path('start/<int:pk>', views.Start, name='start'),   #ここの<int:pk>の部分

pk = task.id   #pkは整数(INT)型


このpk = task.idという情報を持ったままviews.pyのStartビューが実行されます。
つまり
def Start(request, pk): ⇒ def Start(request, task.id):
というように解釈できます。


ビューの続きを見ていきましょう。
まず書き換え対象のモデルを変数「task」に格納します。
task = get_object_or_404(Task, pk=pk)
ここでTaskモデルのpk(プライマリーキー)をリクエストで受け取ったpk(=task.id)で指定しています。
こうすることで着手ボタンが押されたタスクと同じタスクモデルをビュー内に呼び出すことができます。


続いて「実行中」ステータスを割り当てます。
new_status = Status.objects.get(status="Ongoing")
task.status = new_status
まず「new_status」という変数を用意し、Statusモデルの「status="Ongoing"」を格納します。

「task.status」はStatusモデルから選択する形式ですので「task.status = "Ongoing"」という割り当て方はできません。
これはmodels.pyを見たら分かります。
models.py
status = models.ForeignKey(
Status, verbose_name='状況', on_delete=models.PROTECT)
「Status」というモデルからForeignKeyで1つ選択する形式ですね。

上記の理由の為、「new_status」というStatusモデル(status="Ongoing")を用意する必要があるのです。


このStatusモデルをTask.statusに割り当てます。
task.status = new_status
これで、task.statusが "Ongoing"(実行中)に書き換えられました。

書き換えた後はsave()メソッドでモデルを上書き保存します。
task.save()


モデルの更新が終わったので、元のトップページにリダイレクトします。
return redirect('/taskapp')


これでワンクリック変更が完了です。



4.2 - モデル編集


上記のタスク管理の中に歯車アイコンがありますね。
ここを押すと該当タスクの詳細を変更することができます。



上記の例ではタスクの優先度パラメータを「最優先(Urgent」から「低い(Low)」に変更しています。
この編集方法を解説します。


html上の歯車ボタンは下記のように記述し、アイコンが押されたタスクのidを参照し、編集ページに移動するようにします。
html
<a href="/taskapp/edit/{{task.id}}" title="編集"><i class="fas fa-cog"></i></a>
歯車アイコンはFont Awesomeを使用しています。<i class="fas fa-cog"></i>の部分です。
Font Awesomeについては別途記事を書こうと思います。ここでは便利なアイコンライブラリと思っておけば大丈夫です。

まだurls.pyに上記ページの設定を作っていないので、作ります。

urls.py
urlpatterns = [
path('', views.TaskView.as_view(), name='top_page'),
path('edit/<int:pk>', views.TaskEditView, name='edit'), #ここを追加
path('start/<int:pk>', views.Start, name='start'),
]
これで歯車アイコンが押されたときに「TaskEditView」というビューが実行される準備ができました。


次にviews.pyに実行するビューを作成します。
views.py
def TaskEditView(request, pk):
"""Edit Page"""
task = get_object_or_404(Task, pk=pk)

if request.method == "POST":
form = AddTaskForm(request.POST, instance=task)
if form.is_valid():
form.save()
return redirect('/taskapp')
else:
form = AddTaskForm(instance=task)

return render(request, 'task_edit.html', {'task': task, 'form': form})
先ほどのワンクリック変更と若干異なりますね。共通部分の説明は割愛します。

まず「if request.method == "POST":」の部分は飛ばして「else:」文を見ましょう。
この編集ページを開くときはGETメソッドで開かれるので、「if request.method == "POST":」は実行されず「else:」が実行されます。

form = AddTaskForm(instance=task)
「form」という変数に「AddTaskForm」というフォームが格納されています。
ついでに「instance=task」となっていますね。

AddTaskFormはタスクを追加するときに使用するフォームのことです。
forms.pyで定義しています。
forms.py
class AddTaskForm(forms.ModelForm):
"""タスク追加フォーム"""
class Meta:
model = Task
fields = '__all__'
このタスク追加フォームを編集画面でも再利用します。

「instance=task」としたことで、このフォーム入力画面には既に登録済みのパラメータが入力された状態で表示されるようになります。

新規追加画面の場合は何もフォームには入っていません。



編集画面ではフォームに元々のデータが入った状態で表示されます。



ビューの続きを見ましょう。
return render(request, 'task_edit.html', {'task': task, 'form': form})
「task_edit.html」を表示しつつ、テンプレートに先ほど定義した「form」変数を渡しています。


task_edit.htmlの中身を確認します。
task_edit.html
<form method="POST" class="post-form" class="form-group">
{{ form.non_field_errors }}
<label for="{{ form.task.id_for_label }}">やること:</label>
{{ form.task }}
<label for="{{ form.priority.id_for_label }}">優先度:</label>
{{ form.priority }}
<label for="{{ form.status.id_for_label }}">状況:</label>
{{ form.status }}
{{ form.errors }}
{% csrf_token %}
<div>
<a class="btn btn-outline-danger" href="/taskapp" role="button">
キャンセル
</a>
<button type="submit" class="save btn btn-primary">変更する</button>
</div>
</form>
見易いように最低限の項目に絞って書いています。
for文で書けばもっとスッキリしますが、こちらもあえて(略)です。

instance=taskとしたので、ここで表示している3項目のフォーム

「form.task」
「form.priority」
「form.status」


には既にtaskに登録済みのパラメータが入力された状態で表示されます。


さて、それでは編集を行いましょう。
編集画面の「優先度」フォームは「最優先(Urgent)」となっていますので、「低い(Low)」にします。



その後、「変更する」ボタンを押します。
「変更する」ボタンはhtml上ではこのように書かれています。
<button type="submit" class="save btn btn-primary">変更する</button>
「button type="submit"」なので、ボタンを押すとフォームの入力内容が送信されます。

フォームが送信(=POST)されると、views.pyで先ほど説明を飛ばした「if request.method == "POST":」の部分が実行されます。
views.py
if request.method == "POST":
form = AddTaskForm(request.POST, instance=task)
if form.is_valid():
form.save()
return redirect('/taskapp')
「form」という変数に送信されたフォームの内容を入れます。
ここで重要なのが第二引数の「instance=task」の部分です。

この引数が無いとDjangoは送信されたデータが新しいTaskモデルだと認識するので、新規タスクの追加になってしまいます。
当初の目的はタスクの編集(=上書き)ですので、上書きする対象を第二引数で指定します。


その後、通常通りフォームの入力内容にミスが無ければタスクの保存処理に移ります。
if form.is_valid():
form.save()



上書き保存が完了した後、元のトップページにリダイレクトします。
return redirect('/taskapp')


これで編集作業が完了となります。


5. さいごに


以上、2通りのモデル変更方法を解説しました。
実際に自分でいじってみると理解が深まると思いますので、失敗しながらトライしてみて下さい。

お疲れ様でした。