【初めてのDjango】(発展編 #07)UpdateViewを用いてデータベース編集

本記事では「初めてのDjango(発展編)」として、初めてDjangoというフレームワークを使用してWebアプリケーションを作ってみたい!という方向けに、簡単なアプリケーションを作成しながら基本的な知識をご紹介していきます。

本シリーズの全容はこのようになっております。

今回の内容

・UpdateViewを使用して、データベースの1レコード分の情報を修正する

本記事単体でご覧になる方へ

本記事は連作となっており、こちら(GitHub)のコードの続きとしてご紹介させていただきます。本記事単体でもご覧いただけますが、もし上記のコードを引用して学びたい方は下記の流れに沿って準備を行なってください。

STEP
コードのダウンロード

こちらにアクセスしましょう。

「Download ZIP」をクリックし、コードをダウンロード。

STEP
settings.json

先ほどダウンロードしたファイル群を任意のディレクトリに配置します。

その後、プロジェクトのルートディレクトリ(manage.pyがあるとこ)に settings.json を新規作成し、下記の内容を記述してください。

{
    "SECRET_KEY":"<djangoのシークレットキー>",
    "EMAIL_HOST":"<Googleアカウントのメールアドレス>",
    "EMAIL_HOST_PASS":"<Googleのアプリパスワード>",
    "DB_USER": "<PostgreSQLのユーザー名>",
    "DB_PASSWORD": "<PostgreSQLのパスワード>"
}
STEP
データベース設定

下記の記事を参考にデータベースの設定を行います(既にお済みの方は不要です)。

STEP
ローカルサーバーの立ち上げ

ターミナルでプロジェクトのルートディレクトリに移動し、下記のコマンドによりローカルサーバーを立ち上げましょう。

(venv)$ python3 manage.py runserver

その後 http://127.0.0.1:8000 にアクセスします。

このような画面が表示されればOKです!

STEP
管理サイトから商品メニューデータを登録する

こちらを参考に、メニューの追加を行なってください。

目次

何を実装するか

今回は UpdateView という汎用ビューを使用して、デーブルデータの1レコード分の情報を修正する方法をご紹介いたします。

実際の画面上の流れはこんな感じです。

このようにメニューをアプリケーション上で編集できるようなページを作成し、「編集」ボタンによりそのページに遷移させています。

今回の編集ページまでの流れと位置づけはこのようになっております。

このように各ページの役割によってビューを使い分けています。今回は UpdateView を使用します。

ListView、DetailViewによるページの作成方法については下記の記事をご覧ください。

それでは実際に実装する流れを解説していきます!

メニュー編集機能を実装する

UpdateViewを使用して個別編集ページを実装するまでに作成・編集するファイル群は下記の通りです。

ファイル名役割
urls.pyルーティング
views.pyUpdateViewを用いて1レコード情報を取得し、編集するためのフォーム情報などをテンプレートに渡す
menu_update.htmlviews.pyから受け取ったデータを使用して編集ページを作成する
menu_detail.html編集ページへのリンクを追加

それでは一つずつ編集していきます。

ルーティングの設定

まずルーティング設定を行います。アプリケーション内の urls.py を下記のように編集します。

from django.urls import path
from . import views

app_name = 'cafe_app'
urlpatterns = [
    # ... 略 ...
    path('menu-detail/<int:pk>/', views.MenuDetailView.as_view(), name="menu_detail"), 
    path('menu-update/<int:pk>/', views.MenuUpdateView.as_view(), name="menu_update"), 
]

冒頭でも述べましたが、今回は1レコード分のみの情報を特定して取得し、編集する必要があります。したがって、URLに対象のレコードを特定できるような情報を含め、紐づける必要があります。

もう少し噛み砕いて話すと、「https://<ホスト名>/menu-detail」のみでは「どのレコード情報を編集したいの??」となってしまいますので、「https://<ホスト名>/menu-detail/<int:pk>/」としてレコードを特定するような情報を含めています。

pk」はDjangoでデフォルトでモデルの主キーを表しています。学校における出席番号のようなもので、あるデータベース(学校)内のテーブル(教室)から主キー(出席番号)を用いて1レコード(生徒)を特定することができるものです。

ちなみにこの主キーの名前「pk」は変更することができます。

ビューの作成

1レコード分のデータを編集するために、UpdateViewを使用してビューを新規作成していきます。アプリケーションディレクトリ内の views.py に下記のようにビューを新規作成しましょう。

from django.urls import reverse_lazy
from django.views import generic
from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin

from .forms import ContactForm, CreateMenuForm
from .models import CafeMenu

# ... 略 ...

class MenuUpdateView(LoginRequiredMixin, generic.UpdateView):
    model = CafeMenu
    template_name = 'menu_update.html'
    form_class = CreateMenuForm

    # URLが動的に変化するページに遷移する場合に用いる
    def get_success_url(self):
        return reverse_lazy('cafe_app:menu_detail', kwargs={'pk': self.kwargs['pk']})
    
    # 処理が成功した時に実行される
    def form_valid(self, form):
        messages.success(self.request, 'メニューを更新しました')
        return super().form_valid(form)
    
    # 処理が失敗した時に実行される
    def form_invalid(self, form):
        messages.error(self.request, 'メニューの更新に失敗しました')
        return super().form_invalid(form)

新たにMenuUpdateViewというビューを作成しました。ログインしている会員のみアクセスできるように LoginRequiredMixin を継承しており、さらに特定のデータベースを編集できるように UpdateView を継承しています。

フォームフィールドの定義

メニューを編集するためのフォームフィールドは、メニューを作成するためのフォームと変わらないので、CreateMenuFormをそのまま使用しています。

form_class = CreateMenuForm
(参考)models.py
from accounts.models import CustomUser
from django.db import models


class CafeMenu(models.Model):
    """カフェのメニューのモデル"""

    # カスタムユーザーモデルから引っ張ってくる.
    user = models.ForeignKey(CustomUser, verbose_name='ユーザー', on_delete=models.PROTECT)
    photo = models.ImageField(verbose_name='写真', blank=True, null=True)
    name = models.CharField(verbose_name='商品名', max_length=255, default='商品名')
    price = models.PositiveIntegerField(verbose_name='値段')
    description = models.CharField(verbose_name='説明', max_length=255)
    created_at = models.DateTimeField(verbose_name='作成日時', auto_now_add=True)
    updated_at = models.DateTimeField(verbose_name='更新日時', auto_now=True)

    class Meta:
        # 管理画面での表示名を指定。
        verbose_name_plural = 'Cafe_App'
    
    # 管理画面でレコードを判明できるように表示されるものを設定.
    def __str__(self):
        return self.name

カスタムユーザーモデルについてはこちらで解説しております。

(参考)forms.py
import os
import json
from pathlib import Path
from django import forms
from django.core.mail import EmailMessage

from .models import CafeMenu

# ... 略 ...

class CreateMenuForm(forms.ModelForm):
    class Meta:
        # CafeMenuモデルの項目を引用
        model = CafeMenu
        fields = ('photo', 'name', 'price', 'description')

処理完了後の移動するページの設定

def get_success_url(self):
    return reverse_lazy('cafe_app:menu_detail', kwargs={'pk': self.kwargs['pk']})

編集処理が完了した後のページを、この get_success_url()メソッドにて定義しています。

本記事のシリーズにて、今まで reverse_lazy というクラス変数をオーバーライドすることで処理完了後のページを設定していました。ではこの2つの違いは何でしょうか??

それは「URLが動的か動的でないか」です。

今回はメニュー編集後、その編集したメニューの詳細ページに戻るように設定しています。なので、編集したメニューによって遷移先のURLが変化します。すなわち動的なURL設定になるので、get_success_url()メソッドを使用しています。

今回はDjangoでデフォルトで設定されている「pk(主キー)」を使用しています。

編集が成功 / 失敗したときの処理

form_valid()、form_invalid()メソッド内に、それぞれ編集が成功 / 失敗した時の処理を記述しています。

# 編集が成功した時の処理
def form_valid(self, form):
    messages.success(self.request, 'メニューを更新しました')
    return super().form_valid(form)
    
# 編集が失敗した時の処理
def form_invalid(self, form):
    messages.error(self.request, 'メニューの更新に失敗しました')
    return super().form_invalid(form)

mesagesについてはこちらで紹介しております。

テンプレートの作成

先ほど作成した MenuUpdateView を用いて編集ページのテンプレートを作成します。

「cafe_app/templates/」ディレクトリに menu_update.html を新規作成し、下記のように編集してください。

{% extends 'base.html' %}

{% load static %}

{% block title %}お問い合わせ{% endblock title %}

{% block contents %}
<section class="sectionContact">
    <div class="wrapperHeader">
        <h2>UPDATE</h2>
    </div>
    <div class="wrapperUpdateForm">
        <form class="formUpdate" method="post" enctype="multipart/form-data">
            {% csrf_token %}

            {{ form.non_field_errors }}

            {{ form.as_p }}
            <div class="wrapperUpdateBtns">
                <button class="btn btnUpdate" type="submit">更 新</button>
                <a class="btn btnCancel" href="{% url 'cafe_app:menu_detail' object.pk %}"><p>キャンセル</p></a>
            </div>
        </form>
    </div>
    <a class="backToHome" href="{% url 'cafe_app:index' %}"><<<ホームに戻る</a>
</section>
{% endblock contents %}

ファイルのアップロードができるように

ファイルのアップロードができるように、<form>タグにenctype属性を定義しています。

<form class="formUpdate" method="post" enctype="multipart/form-data">

編集用のフォーム

編集用のフォームは、{{ form.as_p }} と一行記述するだけで表示させることができます。

{{ form.as_p }}

「更新」「キャンセル」ボタン

今回は2種類のボタンを設置し、それぞれ下記のような役割を持っています。

  • 「更新」ボタン→データベースを更新し、詳細ページに戻る
  • 「キャンセル」ボタン→詳細ページに戻る

「更新」ボタンを押した場合のページ遷移処理は views.py内の get_success_url()メソッド内で記述していますが、キャンセルの場合のページ遷移は<a>タグを用いて実現しています。この<a>タグ内のherf属性は下記のようになっています。

href="{% url 'cafe_app:menu_detail' object.pk %}

このように記述することで「~/menu-detail/<object.pk>/」というURLを生成してくれます。

すなわち、編集しようとしたメニューの詳細ページへのリンクを作成してくれます。

CSSの適用

見た目を整えるために、CSSを適用させましょう。

mystyle.css
/* メニュー更新フォーム */

.wrapperUpdateForm {
    width: fit-content;
    margin: 0 auto;
}

.formUpdate p {
    display: block;
    margin: 30px;
}

.formUpdate input[type="text"], .formUpdate input[type="number"] {
    background-color: inherit;
    width: 450px;
    height: 24px;
    font-size: 16px;
    border: none;
    border-bottom: solid var(--dark-color) 1px;
    margin-left: 10px;
}

.formUpdate input[type="text"]:focus, .formUpdate input[type="number"]:focus {
    outline: none;
    border-bottom: solid var(--accent-color) 2px;
}

.wrapperUpdateBtns {
    display: flex;
    flex-direction: row;
    justify-content: center;
}

.wrapperUpdateBtns .btn {
    width: 200px;
    height: 50px;
    box-shadow: rgba(0, 0, 0, 0.12) 0px 1px 3px, rgba(0, 0, 0, 0.24) 0px 1px 2px;
    font-size: 20px;
    transition-duration: .2s;
}

.wrapperUpdateBtns .btn:hover {
    transform: translateY(-2px);
    opacity: .9;
    cursor: pointer;
}

.wrapperUpdateBtns .btnUpdate {
    background-color: var(--accent-color);
    display: block;
    border: solid var(--semi-dark-color) 1px;
    margin-right: 5px;
}

.wrapperUpdateBtns .btnCancel {
    background-color: var(--semi-dark-color);
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    color: var(--base-color);
    margin-left: 5px;
}

.wrapperUpdateBtns .btnCancel p {
    display: block;
    width: fit-content;
    margin: 0;
}

見た目に関してはお好みのスタイルを適用していただいて構いません。

リンクの作成

メニュー編集ページは作成できましたので、そのページへ移動するための「編集」ボタンを詳細ページに作成しましょう。メニュー詳細ページである menu_detail.html を下記のように編集しましょう。

{% extends 'base.html' %}

{% load static %}

{% block title %}メニュー詳細{% endblock title %}

{% block contents %}
<section class="sectionMenu">
    <div class="wrapperHeader">
        <h2>MENU DETAIL</h2>
    </div>
    <div class="wrapperMenu">
        <ul class="ulMenu">
            <li class="menuContent">
                {% if object.photo %}
                    <img src="{{ object.photo.url }}" alt="{{ object.name }}">
                {% endif %}
                <h3>{{ object.name }}</h3>
                <p class="price">{{ object.price }}円</p>
                <p class="description">{{ object.description }}</p>
                <hr>
                <p class="created_at">作成日時:{{ object.created_at }}</p>
                <p class="updated_at">更新日時:{{ object.updated_at }}</p>
            </li>
        </ul>
        <div class="wrapperEditBtns">
            <a class="btn-update" href="{% url 'cafe_app:menu_update' object.pk %}">編 集</a>
        </div>
    </div>
    <a class="backToHome" href="{% url 'cafe_app:index' %}"><<<ホームに戻る</a>
</section>
{% endblock contents %}

ハイライトした行のみ追加しました。ここのリンク先URLの作成方法は先ほど述べた方法と同様です。object.pk を引数としてルーティングに渡しています

<a class="btn-update" href="{% url 'cafe_app:menu_update' object.pk %}">編 集</a>

「編集」ボタンの見た目をCSSで整えましょう。

mystyle.css
.wrapperEditBtns {
    display: flex;
    flex-direction: row;
    justify-content: center;
}

.wrapperEditBtns a {
    padding: 10px 30px;
    font-size: 16px;
    margin: 0 5px;
    color: var(--dark-color);
    transition-duration: .2s;
}

.wrapperEditBtns a:hover {
    cursor: pointer;
    opacity: .9;
    transform: translateY(-2px);
}

.btn-update {
    background-color: var(--accent-color);
    border: none;
}

すると詳細ページにこんな感じの「編集」ボタンが作成できます。

編集ボタンをクリックすると、メニュー編集ページに遷移します。ここでメニューの各項目を編集することができます。

メニューの情報を更新、すなわちデータベースの情報を更新する機能を実装することができました!お疲れ様でした!

この時点でのコード全体をGithubに載せております。確認用としてお使いいただければと思います。

まとめ

本セクションでは、UpdateViewというビュー使用してデータベースの情報を編集する機能を実装する方法をご紹介しました。ビューにモデルとフォーム情報を渡せば比較的簡単に実装できるのですが、各ルーティングの考え方が少し難しいかと思います、、、。

次回は、DeleteViewを用いて作成したデータベースの情報を削除する方法をご紹介いたします。

ご覧いただきありがとうございました!

案件、ありますか?

「メインの仕事があるけれど、週1、2日だけできる仕事ないかな、、、」

「ある程度スキルが身に付いてはきたけど、そのスキルを活用できる場が欲しい」

なんて悩みが以前はありました。

自分で仕事を探しに行くのも大事ですが、蛇の道は蛇。その道の人に頼むことで、自分だけでは見つからないような案件に携わることができます。

IT PRO パートナーズでは、簡単に無料でアカウントを登録でき、さらにはエージェントさんに希望の働き方・案件の種類を提示することでお仕事を紹介してくれます!

登録自体も非常に簡単で、「エージェントさんとの面談を希望する」という欄にチェックをするだけで、エージェントさんから直接連絡をいただくことができます。

驚くほど簡単で正直拍子抜けしてしまいました笑

もしお仕事探しに困っておりましたら、一度登録し案件を眺めてみることをおすすめします!

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

勤めていた設計会社を退社し、フリーランスとして活動しています
また、趣味で主にpyhonを用いて機械学習を行なっています!
現在競艇の予測モデルの開発中です。

コメント

コメント一覧 (2件)

コメントする

目次