Python Django お問い合わせ管理サイトを作る その6(CRUDの実装)

Django

今回は、登録済みデータの更新、削除機能を実装していきます。

データの修正機能を作成する

データの更新機能で使うテンプレートファイルは、新規作成のときと同じinquiry_form.htmlを利用することができます。
更新用のview関数の中で、登録済みのデータをテンプレートに渡してあげればOKです。

views.py
def inquiry_update(request, pk):
    inquiry = Inquiry.objects.get(id=pk)
    form = InquiryModelForm(instance=inquiry)
    
    if request.method == 'POST':
        form = InquiryModelForm(request.POST, instance=inquiry)
        
        if form.is_valid():
            form.save()
            return redirect('DjangoApp:inquiry-list')
            
    context = {
        'form': form, 
    }
        
    return render(request, 'DjangoApp/inquiry_form.html', context)

更新対象のデータを取得して、フォームのインスタンスとして指定しています。
こうすることで、新規作成と同じフォームを利用しながら、登録済みのデータを初期値として
各フィールドにセットすることができます。

更新の保存処理自体は、新規作成の処理と同じです。
Djangoでは、同じsave()メソッドで新規作成・更新を行うことができます。
プライマリキー属性がセットされている(Noneや空文字列等でない場合)には、Updateが実行されます。そうでない場合、または、Update実行操作によりいずれのデータも更新されなかった場合には、Insert処理が実行されます。

URL情報を追加します。

DjangoApp/urls.py
from django.urls import path

from .views import dashboard, inquiry_create, inquiry_list, inquiry_detail, inquiry_update


app_name = 'DjangoApp'
urlpatterns = [
    path('', dashboard, name='dashboard'),
    path('create/', inquiry_create, name='inquiry-create'),
    path('list/', inquiry_list, name='inquiry-list'),
    path('<int:pk>/', inquiry_detail, name='inquiry-detail'),
    path('update/<int:pk>/', inquiry_update, name='inquiry-update'),

]

更新するレコードを特定しないといけないので、<int:pk>を入れています。

では、試してみましょう。

入力フォームにはじめから値がセットされていますね。
適当に値を変えて、送信ボタンを押してみてください。

詳細ページで確認すると、値が更新されているのがわかります。

詳細表示ボタン、更新ボタン、削除ボタンを追加する

削除機能を追加する前に、少しテンプレートに手を加えます。
これまで、各ページへ飛ぶのにURLを直打ちしてきましたが、実際にはボタン等でページを移動します。

まず、ページ上部のメニューにリンク先を設定します。
Inquiryメニューをクリックしたときには、受付機能のトップページ(inquiry-dashboard)を表示できるようにします。

inquiry_dashboard.html
{% extends 'DjangoApp/base.html' %}

{% block content %}
<div class="row col-md-12">
    <div class="col col-md-2">
        {% include 'DjangoApp/inquiry_menu.html' %}
    </div>
    <div class="col col-md-10">
        <h1>コンテンツの中身</h1>
    </div>
</div>
{% endblock %}

DjangoApp/urls.py
from django.urls import path

from .views import (
    dashboard,
    inquiry_dashboard, 
    inquiry_create,
    inquiry_list,
    inquiry_detail,
    inquiry_update)

app_name = 'DjangoApp'
urlpatterns = [
    path('', dashboard, name='dashboard'),
    path('dashboard/', inquiry_dashboard, name='inquiry-dashboard'), 
    path('create/', inquiry_create, name='inquiry-create'),
    path('list/', inquiry_list, name='inquiry-list'),
    path('<int:pk>/', inquiry_detail, name='inquiry-detail'),
    path('update/<int:pk>/', inquiry_update, name='inquiry-update'),


]

トップページ用のview関数を定義します。

views.py
def inquiry_dashboard(request):
    return render(request, 'DjangoApp/inquiry_dashboard.html', {})

ヘッダーのリンク情報を修正します。

header.html
{% load static %}

<nav class="navbar navbar-expand-md navbar-dark bg-dark justify-content-between"">
    <img src=" {% static 'DjangoApp/img/logo.png' %}" alt="logo">
    <ul class="navbar-nav">
        <li class="nav-item"><a href="{% url 'DjangoApp:dashboard' %}" class="nav-link">Dashboad</a></li>
        <li class="nav-item"><a href="{% url 'DjangoApp:inquiry-dashboard' %}" class="nav-link">Inquiry</a></li>
        <li class="nav-item"><a href="" class="nav-link">About</a></li>
    </ul>
</nav>

リンク先を表すhref属性は、以下の形式で指定しています。

{% url 'リンク先' パラメータ %}

リンク先には、views.urls.pyで定義したnameを入力します。
この部分では、パラメータは不要ですが、レコードのプライマリーキー情報を指定する必要がある場合など(<int:pk>の情報)、パラメータの部分に各レコードのidを入力してしたります。

次にナビゲーションメニューのメニュー2に「受付一覧」へのリンク情報を追加します。
ダッシュボードメニューにもリンク先を指定しておきます。
また、Inquiryメニューを押したときは、ダッシュボードを開くようにしておきます。

inquiry_menu.html
<nav class="navbar">
    <ul class="navbar-nav">
        <li class="nav-item"><a href="{% url 'DjangoApp:inquiry-dashboard' %}" class="nav-link">ダッシュボード</a></li>
        <li class="nav-item"><a href="{% url 'DjangoApp:inquiry-link' %}" class="nav-link">受付一覧</a></li>
        <li class="nav-item"><a href="" class="nav-link">メニュー3</a></li>
    </ul>
</nav>

次に、一覧ページを修正します。

inquiry_list.html
{% extends 'DjangoApp/base.html' %}

{% block content %}
<div class="row">
    <div class="col col-md-2">
        {% include 'DjangoApp/inquiry_menu.html' %}
    </div>
    <div class="col col-md-10">
        <div class="row my-2 border-bottom bor-der-primary">
            <h2 class="pl-2">受付一覧</h2>
        </div>
        <table class="table table-dm">
            <tr>
                <th></th>
                <th>緊急度</th>
                <th>ステータス</th>
                <th>件名</th>
                <th>顧客名</th>
                <th>受付日時</th>
            </tr>
            {% for item in inquirys %}
            <tr>
                <td>
                    <a href="{% url 'DjangoApp:inquiry-detail' item.id %}" class="btn btn-info">表示</a>
                    <a href="{% url 'DjangoApp:inquiry-update' item.id %}" class="btn btn-primary">更新</a>
                    <a href="" class="btn btn-warning">削除</a>
                </td>
                <td>{{ item.get_emergency_display }}</td>
                <td>{{ item.get_status_display }}</td>
                <td>{{ item.subject }}</td>
                <td>{{ item.customer }}</td>
                <td>{{ item.reception_datetime }}</td>
            </tr>
            {% endfor %}
        </table>
    </div>
</div>

{% endblock %}

一覧テーブルで空にしておいた<td>タグのところに「表示」、「更新」、「削除」リンクを追加しました。クラスによって、ボタン風に表示させます。

削除機能は未定義ですのでとりあえず、空にしてあります。

表示ボタンや更新ボタンを押してみてください。それぞれの表示に正しく切り替わりますよね。

また、詳細表示ページからも更新や削除へ飛べたらいいですよね。追加します。

inquiry_detail.html
{% extends 'DjangoApp/base.html' %}
{% load verbose_name %}

{% block content %}
<div class="row">
    <div class="col col-md-2">
        {% include 'DjangoApp/inquiry_menu.html' %}
    </div>
    <div class="col col-md-10">
        <div class="row my-2 border-bottom bor-der-primary">
            <h2 class="pl-2">受付入力</h2>
        </div>
        <div class="row pl-2">
            <div class="row col-md-12">
                <p class="col col-md-2">{% get_verbose_field_name inquiry 'emergency' %}</p>
                <p class="col col-md-10 text-primary">{{ inquiry.get_emergency_display }}</p>
            </div>
            <div class="row col-md-12">
                <p class="col col-md-2">{% get_verbose_field_name inquiry 'status' %}</p>
                <p class="col col-md-10 text-primary">{{ inquiry.get_status_display }}</p>
            </div>
            <div class="row col-md-12">
                <p class="col col-md-2">{% get_verbose_field_name inquiry 'subject' %}</p>
                <p class="col col-md-10 text-primary">{{ inquiry.subject }}</p>
            </div>
            <div class="row col-md-12">
                <p class="col col-md-2">{% get_verbose_field_name inquiry 'customer' %}</p>
                <p class="col col-md-10 text-primary">{{ inquiry.customer }}</p>
            </div>
            <div class="row col-md-12">
                <p class="col col-md-2">{% get_verbose_field_name inquiry 'category' %}</p>
                <p class="col col-md-10 text-primary">{{ inquiry.category }}</p>
            </div>
            <div class="row col-md-12">
                <p class="col col-md-2">{% get_verbose_field_name inquiry 'reception_datetime' %}</p>
                <p class="col col-md-10 text-primary">{{ inquiry.reception_datetime }}</p>
            </div>
            <div class="row col-md-12">
                <p class="col col-md-2">{% get_verbose_field_name inquiry 'content' %}</p>
                <p class="col col-md-10 text-primary">{{ inquiry.content }}</p>
            </div>
            <div class="row col-md-12">
                <p class="col col-md-2">{% get_verbose_field_name inquiry 'request' %}</p>
                <p class="col col-md-10 text-primary">{{ inquiry.request }}</p>
            </div>
        </div>
        <div class="row col-pl2">
            <div class="row col-md-12">
                <a href="{% url 'DjangoApp:inquiry-update' inquiry.id %}" class="btn btn-primary">更新</a>
                <a href="" class="offset-md-10 btn btn-warning">削除</a>
            </div>
        </div>
    </div>
</div>
{% endblock %}

とりあえず、ページ遷移の処理はこれくらいでしょうか。

削除機能を作成する。

削除ページは、データの詳細ページに削除確認メッセージを追加する形で行きましょう。

inquiry_delete.html
{% extends 'DjangoApp/base.html' %}
{% load verbose_name %}

{% block content %}
<div class="row">
    <div class="col col-md-2">
        {% include 'DjangoApp/inquiry_menu.html' %}
    </div>
    <div class="col col-md-10">
        <div class="row my-2 border-bottom bor-der-primary">
            <h2 class="pl-2">受付削除</h2>
        </div>
        <div class="row pl-2">
            <label for="" class="text-danger">本当にこのレコードを削除してもよろしいですか?</label>
        </div>
        <div class="row pl-2">
            <div class="row col-md-12">
                <p class="col col-md-2">{% get_verbose_field_name inquiry 'emergency' %}</p>
                <p class="col col-md-10 text-primary">{{ inquiry.get_emergency_display }}</p>
            </div>
            <div class="row col-md-12">
                <p class="col col-md-2">{% get_verbose_field_name inquiry 'status' %}</p>
                <p class="col col-md-10 text-primary">{{ inquiry.get_status_display }}</p>
            </div>
            <div class="row col-md-12">
                <p class="col col-md-2">{% get_verbose_field_name inquiry 'subject' %}</p>
                <p class="col col-md-10 text-primary">{{ inquiry.subject }}</p>
            </div>
            <div class="row col-md-12">
                <p class="col col-md-2">{% get_verbose_field_name inquiry 'customer' %}</p>
                <p class="col col-md-10 text-primary">{{ inquiry.customer }}</p>
            </div>
            <div class="row col-md-12">
                <p class="col col-md-2">{% get_verbose_field_name inquiry 'category' %}</p>
                <p class="col col-md-10 text-primary">{{ inquiry.category }}</p>
            </div>
            <div class="row col-md-12">
                <p class="col col-md-2">{% get_verbose_field_name inquiry 'reception_datetime' %}</p>
                <p class="col col-md-10 text-primary">{{ inquiry.reception_datetime }}</p>
            </div>
            <div class="row col-md-12">
                <p class="col col-md-2">{% get_verbose_field_name inquiry 'content' %}</p>
                <p class="col col-md-10 text-primary">{{ inquiry.content }}</p>
            </div>
            <div class="row col-md-12">
                <p class="col col-md-2">{% get_verbose_field_name inquiry 'request' %}</p>
                <p class="col col-md-10 text-primary">{{ inquiry.request }}</p>
            </div>
        </div>
        <div class="row pl-2">
            <div class="row col-md-12">
                <div class="col">
                    <form action="{% url 'DjangoApp:inquiry-delete' inquiry.id %}" class="form-group" method="POST">
                        {% csrf_token %}
                        <a href="{{ request.META.HTTP_REFERER }}">戻る</a>
                        <input type="submit" name="submit" value='削除' class="offset-md-10 btn-danger">
                    </form>
                </div>
            </div>
        </div>
    </div>
</div>
{% endblock %}

POSTされたときに、対象レコードを特定するためにacrion属性には対象のレコードidを付与したURLを指定しています。
この情報をもとにPOSTされたときにレコードの削除を実行します。

views.py
ef inquiry_delete(request, pk):
    inquiry = Inquiry.objects.get(id=pk)
    
    if request.method == 'POST':
        inquiry.delete()
        return redirect('DjangoApp:inquiry-list')

    context = {
        'inquiry': inquiry, 
    }
    
    return render(request, 'DjangoApp/inquiry_delete.html', context)

POSTされたときには、対象のオブジェクトのdeleteメソッドを呼んであげるだけでOKです。
完了後は、一覧ページに遷移するようにします。

DjangoApp/urls.py
from django.urls import path

from .views import (
    dashboard,
    inquiry_dashboard, 
    inquiry_create,
    inquiry_list,
    inquiry_detail,
    inquiry_update,
    inquiry_delete, )

app_name = 'DjangoApp'
urlpatterns = [
    path('', dashboard, name='dashboard'),
    path('dashboard/', inquiry_dashboard, name='inquiry-dashboard'), 
    path('create/', inquiry_create, name='inquiry-create'),
    path('list/', inquiry_list, name='inquiry-list'),
    path('<int:pk>/', inquiry_detail, name='inquiry-detail'),
    path('update/<int:pk>/', inquiry_update, name='inquiry-update'),
    path('delete/<int:pk>/', inquiry_delete, name='inquiry-delete'),


]

最後に先程空にしておいた削除リンクの部分にURLを指定しておきます。
指定するURLは以下のとおりです。

inquiry_detail.html
{% url 'DjangoApp:inquiry-delete' inquiry.id %}
inquiry_list.html
{% url 'DjangoApp:inquiry-delete' item.id %}

では、試してみましょう。

レコードが消えていることがわかりますね。
CRUD機能の実装については、基本的な部分が一通り完了しました。

この手のページでは、他にも検索機能が必要ですね。
受付データが多くなってくると、対象のデータを探すのが大変です。また、いまのままでは、受付一覧に全登録レコードが表示されてしまいます。数件であれば問題ありませんが、1000件などになってくるとスクロールがどこまで続くのやらといった感じですね。
1ページに表示できるレコード件数を絞るためにページネーションと呼ばれる機能の実装も必須と言っていいでしょう。

次回以降は、検索機能の実装とページネーション機能の実装について見ていきます。
いずれもこれまで通りDjangoに基本機能がもれなく用意されていますのでご安心ください。

コメント

タイトルとURLをコピーしました