Django

Pythonフレームワーク「Django」の「ModelForm」を利用してMySQLに接続してみた

Django(ジャンゴ)内の「ModelForm」を利用すると、データベースとアクセスするModelクラスと連動するFormを作成することになり、ユーザーがFormで入力した値をデータベースに直接保存することができる。

今回は、MySQLのデータベースのテーブルにユーザー情報を登録する処理を、ModelFormを利用して実装してみたので、そのサンプルプログラムを共有する。

なお、FormクラスとModelFormクラスの違いについては、以下のサイトを参照のこと。
https://kosuke-space.com/django-modelform

前提条件

下記記事の実装が完了していること。

Pythonフレームワーク「Django」を利用してMySQLのテーブルにデータ登録する処理を実装してみたDjango(ジャンゴ)を利用すると、PythonによるWebアプリケーションを作成することができて、データベースへの接続も行うことがで...

サンプルプログラムの作成

作成したサンプルプログラムの構成は、以下の通り。
サンプルプログラムの構成
なお、上記の赤枠は、前提条件のプログラムから追加・変更したプログラムである。また、「0002_alter_userdata_birth_day_alter_userdata_birth_month_and_more.py」はマイグレーション後に作成されるプログラムのため、ここでは赤枠を付与していない。

demoフォルダ下、models.pyの内容は以下の通りで、ユーザーデータを登録するテーブル(user_data)の各項目に対する指定可能なデータを設定している。また、「__str__」メソッドの追加により、各項目値を文字列で表示できるようにしている。

from django.db import models


class UserData(models.Model):
    """ user_dataテーブルへアクセスするためのモデル """
    # 性別のデータ
    sex_data = [
        ('1', '男'),
        ('2', '女')
    ]
    # 生年月日(月)のデータ
    birth_year_data = [
        (x + 1, x + 1) for x in range(1909, 2022)
    ]
    # 生年月日(月)のデータ
    birth_month_data = [
        (x + 1, x + 1) for x in range(12)
    ]
    # 生年月日(日)のデータ
    birth_day_data = [
        (x + 1, x + 1) for x in range(31)
    ]

    # テーブル名を存在するuser_dataテーブルに変更する
    class Meta:
        db_table = 'user_data'

    id = models.IntegerField(primary_key=True)
    name = models.CharField(max_length=40)
    birth_year = models.IntegerField(choices=birth_year_data, default='2012')
    birth_month = models.IntegerField(choices=birth_month_data, default='1')
    birth_day = models.IntegerField(choices=birth_day_data, default='1')
    sex = models.CharField(choices=sex_data, max_length=1, default='1')
    memo = models.CharField(max_length=1024, blank=True, null=True)

    # print(UserDataオブジェクト)とすることで、UserDataオブジェクトの各値を
    # 文字列で表示できるようにするためのメソッド
    def __str__(self):
        return 'UserData id=' + str(self.id) + ', name=' + str(self.name) \
               + ', birth_year=' + str(self.birth_year) \
               + ', birth_month=' + str(self.birth_month) \
               + ', birth_day=' + str(self.birth_day) \
               + ', sex=' + str(self.sex) + ', memo=' + str(self.memo)

demoフォルダ下、forms.pyの内容は以下の通りで、user_dataテーブルへアクセスするためのモデルに準じた(ModelFormクラスを継承した)フォームであるUserDataModelFormクラスと、UserDataModelFormクラスに項目を追加したInputModelFormクラスを定義している。

from django import forms
from .models import UserData
from django.core.validators import RegexValidator
import datetime


def check_date(year, month, day):
    """ 年月日が存在する日付の場合はTrue、存在しない日付の場合はFalseを返す """
    try:
        new_date_str = "%04d/%02d/%02d" % (year, month, day)
        datetime.datetime.strptime(new_date_str, "%Y/%m/%d")
        return True
    except ValueError:
        return False


class UserDataModelForm(forms.ModelForm):
    """ user_dataテーブルへアクセスするためのモデルに準じたフォーム """
    class Meta:
        # ベースとなるモデルクラス(UserData)を指定
        model = UserData
        # ベースとなるモデルクラスのうち、表示するフィールドを指定
        fields = ('name', 'birth_year', 'birth_month', 'birth_day', 'sex', 'memo')
        labels = {
            'name': '名前',
            'birth_day': '生年月日',
            'sex': '性別',
            'memo': 'メモ',
        }
        widgets = {
            'name': forms.TextInput(attrs={'size': '13', 'maxlength': '13'}),
            'birth_year': forms.Select,
            'birth_month': forms.Select,
            'birth_day': forms.Select,
            'sex': forms.RadioSelect(),
            'memo': forms.Textarea(attrs={'rows': 6, 'cols': 40}),
        }

    def clean(self):
        """ フォーム内の項目の入力チェック処理 """
        cleaned_data = super().clean()
        birth_year = cleaned_data.get('birth_year')
        birth_month = cleaned_data.get('birth_month')
        birth_day = cleaned_data.get('birth_day')

        # 生年月日が存在しない日付の場合、エラーを返す
        if not check_date(birth_year, birth_month, birth_day):
            raise forms.ValidationError('生年月日を正しく入力してください。')
        return cleaned_data


class InputModelForm(UserDataModelForm):
    """ user_dataテーブルへアクセスするためのモデルに準じたフォームに、項目(check)を追加 """
    """ views.pyからは、このInputModelFormクラスを参照している """
    check = forms.BooleanField(label='入力確認')

    class Meta(UserDataModelForm.Meta):
        fields = UserDataModelForm.Meta.fields + ('check',)

なお、上記UserDataModelFormクラス内のcleanメソッドで、独自チェックを実装している。独自チェック処理については、以下のサイトを参照のこと。
https://yu-nix.com/archives/django-form-validation/

demoフォルダ下、views.pyの内容は以下の通りで、InputModelFormクラスを利用した入力チェック処理や、user_dataテーブルへの登録処理を行うようにしている。

from django.shortcuts import render
from django.http import HttpResponse
from .forms import InputModelForm
from .models import UserData
from django.db.models import Max
from datetime import datetime as dt

# クラス定数を定義
SEX_LABEL_IDX = 1  # 性別のラベルを取得するためのインデックス


def index(request):
    """ 入力画面を表示 """
    # 確認画面に渡すフォームを初期化
    params = {
        'input_form': InputModelForm()
    }
    return render(request, 'demo/input.html', params)


def confirm(request):
    """ 入力画面で確認ボタンが押下された時の処理を定義 """
    # 入力画面でのフォーム値を取得
    input_form = InputModelForm(request.POST)

    # フォームの入力チェックを行い、エラーがない場合
    if input_form.is_valid():
        # 確認画面で表示するための入力値(性別)の値を生成
        lbl_sex = UserData.sex_data[int(request.POST.get('sex')) - 1][SEX_LABEL_IDX]

        # 確認画面に渡す各変数を定義
        params = {
            'input_form': input_form,
            'lbl_sex': lbl_sex,
            'lbl_checked': '確認済',
        }
        return render(request, 'demo/confirm.html', params)

    # フォームの入力チェックを行い、エラーがある場合
    else:
        # フォーム値はそのままで入力画面に戻る
        params = {
            'input_form': input_form
        }
        return render(request, 'demo/input.html', params)


def regist(request):
    """ 確認画面でボタンが押下された時の処理を定義 """
    # 送信ボタンが押下された場合
    if "send" in request.POST:
        # USER_DATAテーブルに登録されているidの最大値を取得
        max_id_dict = UserData.objects.all().aggregate(Max('id'))

        # USER_DATAテーブルに入力データを追加
        # USER_DATAテーブルに登録されているデータが無い場合、id=1とする
        user_data = UserData()
        user_data.id = (max_id_dict['id__max'] or 0) + 1

        # 確認画面でのフォーム値にidを設定し、入力データを保存
        input_form = InputModelForm(request.POST, instance=user_data)
        input_form.save()
        return render(request, 'demo/complete.html')

    # 戻るボタンが押下された場合
    elif "back" in request.POST:
        # 確認画面でのフォーム値を入力画面に渡す
        input_form = InputModelForm(request.POST)

        params = {
            'input_form': input_form
        }
        return render(request, 'demo/input.html', params)

また、demoフォルダ下、templatesフォルダ下の入力画面(input.html)・確認画面(confirm.html)の内容は以下の通りで、InputModelFormクラスのフォームを利用するようにしている。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>入力画面</title>
    <!-- 独自CSSファイルの読み込み -->
    {% load static %}
    <link rel="stylesheet" href="{% static 'demo/css/demo.css' %}">
</head>
<body>
    <p>下記必要事項を記載の上、「確認」ボタンを押下してください。</p><br/>
    <!-- action属性のURLで、(demoフォルダ内)urls.pyの画面遷移先のname属性の値を指定している -->
    <form action="{% url 'confirm' %}" method="post">
        <!-- 下記csrf_tokenは、CSRF対策を行うことでform送信時エラーを防ぐために設定 -->
        {% csrf_token %}
        <table>
            <!-- 氏名 -->
            <tr>
                <td align="left" valign="top">{{ input_form.name.label_tag }}</td>
                <td>{{ input_form.name }}</td>
            </tr>
            <!-- 生年月日 -->
            <tr>
                <td align="left" valign="top">{{ input_form.birth_day.label_tag }}</td>
                <td>
                    {{ input_form.birth_year }}年
                    {{ input_form.birth_month }}月
                    {{ input_form.birth_day }}日
                    <!-- 生年月日の入力チェックエラーを表示 -->
                    <span class="error_message">
                        {{ input_form.non_field_errors.0 }}
                    </span>
                </td>
            </tr>
            <!-- 性別 -->
            <tr>
                <td align="left" valign="top">{{ input_form.sex.label_tag }}</td>
                <td>
                    {% for sex_choice in input_form.sex %}
                        {{ sex_choice.choice_label }}
                        {{ sex_choice.tag }}
                    {% endfor %}
                </td>
            </tr>
            <!-- メモ -->
            <tr>
                <td align="left" valign="top">{{ input_form.memo.label_tag }}</td>
                <td>{{ input_form.memo }}</td>
            </tr>
            <!-- 入力確認 -->
            <tr>
                <td align="left" valign="top">{{ input_form.check.label_tag }}</td>
                <td>{{ input_form.check }}</td>
            </tr>
        </table>
        <br/><br/>
        <input type="submit" name="confirm" value="確認" />
    </form>
</body>
</html>
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>確認画面</title>
</head>
<body>
    <p>入力内容を確認し、問題なければ「送信」ボタンを押下してください。</p>
    <form action="{% url 'regist' %}" method="post">
        {% csrf_token %}
        <table>
            <!-- 氏名 -->
            <tr>
                <td align="left" valign="top">{{ input_form.name.label_tag }}</td>
                <td>{{ input_form.name.value }}</td>
            </tr>
            <!-- 生年月日 -->
            <tr>
                <td align="left" valign="top">{{ input_form.birth_day.label_tag }}</td>
                <td>{{ input_form.birth_year.value }}年
                    {{ input_form.birth_month.value }}月
                    {{ input_form.birth_day.value }}日</td>
            </tr>
            <!-- 性別 -->
            <tr>
                <td align="left" valign="top">{{ input_form.sex.label_tag }}</td>
                <td>{{ lbl_sex }}</td>
            </tr>
            <!-- メモ -->
            <tr>
                <td align="left" valign="top">{{ input_form.memo.label_tag }}</td>
                <!-- テキストエリアの改行を有効にするため、「| linebreaksbr」を付与 -->
                <td>{{ input_form.memo.value | linebreaksbr }}</td>
            </tr>
            <!-- 入力確認 -->
            <tr>
                <td align="left" valign="top">{{ input_form.check.label_tag }}</td>
                <td>{{ lbl_checked }}</td>
            </tr>
        </table>
        <br/><br/>
        <!-- フォームの各値を次画面に渡すためhidden項目を設定 -->
        {% for field in input_form %}
             {{field.as_hidden}}
        {% endfor %}
        <input type="submit" name="send" value="送信" />
        <input type="submit" name="back" value="戻る" />
    </form>
</body>
</html>

その他のソースコード内容は、以下のサイトを参照のこと。
https://github.com/purin-it/python/tree/master/django-mysql-modelform/djangoApp



削除または保存していないWordドキュメントの復元方法【4DDiG Windowsデータ復元】ワード(Word)データ等のファイルを誤って削除してしまった場合は、通常はデータの復元ができませんが、4DDiGというソフトウェアを利用...

マイグレーションの実行

前提条件の記事で、マイグレーション(アプリケーションで使うデータベースの定義を自動的に作成・管理する機能)を実行したが、今回もmodels.pyを編集したため、再度マイグレーションを実行する。

マイグレーションの実行方法は、以下の記事の「マイグレーションの実行」を参照のこと。ただし、user_dataテーブルは削除せず、作成済の状態でマイグレーションを実行すること。

Pythonフレームワーク「Django」を利用してMySQLのテーブルにデータ登録する処理を実装してみたDjango(ジャンゴ)を利用すると、PythonによるWebアプリケーションを作成することができて、データベースへの接続も行うことがで...

コマンドプロンプト上で「python manage.py makemigrations (Djangoアプリケーション名)」コマンドを実行すると、以下の赤枠のファイルが作成されることが確認できる。
マイグレーションの実行_1

また、コマンドプロンプト上で「python manage.py showmigrations」コマンドを実行すると、以下のように、マイグレーション履歴が確認できる。
マイグレーションの実行_2

なお、マイグレーション後のuser_dataテーブルの定義を確認すると以下の通りで、マイグレーション前と同じ内容になっている。
マイグレーションの実行_3



フリエン(furien)は多くの案件を保有しフリーランス向けサービスも充実しているエージェントだったフリエン(furien)は、ITフリーランス(個人事業主)エンジニア専門のエージェントであるアン・コンサルティング株式会社が運営する業界...

サンプルプログラムの実行

サンプルプログラムの実行結果は以下の通りで、画面で入力したユーザーデータがuser_dataテーブルに追加されると共に、実行したSQLがログ出力されることが確認できる。

1) 実行前のuser_dataテーブルの中身は、以下の通り。

select * from user_data
サンプルプログラムの実行結果_1

2) コマンドプロンプトでDjangoプロジェクト名のフォルダに移動し、コマンドプロンプトで「python manage.py runserver」コマンドを実行して、Webサーバーを起動する。
サンプルプログラムの実行結果_2

3) 何も入力せず「確認」ボタンを押下すると、以下のようなエラーメッセージが表示されることが確認できる。なお、下記は「名前」の例だが、「入力確認」も同様のエラーメッセージが表示される。
サンプルプログラムの実行結果_3

4) 生年月日に存在しない日付を入力し「確認」ボタンを押下すると、以下のようなエラーメッセージが表示されることが確認できる。
サンプルプログラムの実行結果_4

5) 以下のように、入力画面で値を設定し確認画面で「送信」ボタンを押下すると、画面で入力したユーザーデータがuser_dataテーブルに追加されることが確認できる。また、このときに追加されたidは3(登録済のidの最大値+1)となっている。
サンプルプログラムの実行結果_5_1

サンプルプログラムの実行結果_5_2 サンプルプログラムの実行結果_5_3

6) 実行後のuser_dataテーブルの中身は以下の通りで、id=3のデータが追加されたことが確認できる。

select * from user_data
サンプルプログラムの実行結果_6

7) このときのコンソールログを確認すると、以下の赤枠のように、SQLの実行ログが出力されていることが確認できる。
サンプルプログラムの実行結果_7

要点まとめ

  • Django内の「ModelForm」を利用すると、データベースとアクセスするModelクラスと連動するFormを作成することになり、ユーザーがFormで入力した値をデータベースに直接保存することができる。
  • ModelFormを利用したフォームクラスで項目を追加する場合は、ModelFormクラスを継承したサブクラスを親にもつFormクラスに定義する。