【Django】FormViewからデータをデータベースに保存してみました。

 今回、DjangoでFormViewからSQLiteにデータを保存してみたので投稿します。




 まずは完成版から。最低限必要なことを記載したいのでアプリの作成やプロジェクトでの設定は後ほど記載します。



テンプレートの作成

 フォームを表示するテンプレートを作成します。postが呼ばれたときの動作を定義します。csrf_tokenはセキュリティ上必要なもので、無いとエラーになります。form.as_pでforms.pyで定義している項目を全てp要素として表示します。クリックするとpostを実行するボタンを作成します。


    #アプリ名/forms.html
    <form method="post">
        {% csrf_token %}
        {{ form.as_p }}
        <button type="submit">送信</button>
    {/form}



 データベースに保存されたデータを表示するテンプレートを作成します。object_listはDjangoで準備されている変数なのでそのまま使用します。todo_listはこちらで定義した任意の変数でobject_listに格納された配列をtodo_listに渡してforループで使用します。


    #アプリ名/list.html
    <h1>List</h1>
    <table>
        <thead>
            <tr><th>todo</th><th>date</th><th>detail</th></tr>
        </thead>
        <tbody>
            {% for todo_list in object_list %}
            <tr>
                <td>
                    {{ todo_list.todo }}
                </td>
                <td>
                    **{{ todo_list.date }}**
                </td>
                <td>
                    {{ todo_list.detail }}
                </td>
            </tr>
            {% endfor %}
        </tbody>
    </table>




 form.htmlでpostが成功した時に表示するテンプレートを作成します。


    #アプリ名/reverse.html
    <h1>Reverse</h1>



models.pyの編集

 データベースのテーブルと連携するためにモデルを編集します。


    #アプリ名/models.py
    from django.db import models

    class ToDoModel(models.Model):
        todo = models.CharField(max_length=20)
        detail = models.CharField(max_length=200)
        date = models.DateField()



forms.pyの作成

 フォームを作成してhtmlに表示する項目を定義します。また、フォームに入力されたデータをデータベースに保存するためにsaveメソッドにモデルとの連携の設定をします。


    #アプリ名/forms.py
    from django import forms
    from . import models
    class ToDoForm(forms.Form):
        todo = forms.CharField()
        detail = forms.CharField()
        date = forms.DateField()
        def save(self):
            data = self.cleaned_data
            models.ToDoModel(
                todo = data['todo'],
                detail = data['detail'],
                date = data['date'],
            ).save()



views.pyの編集

 上で作成したテンプレートとモデル、フォームをビューに設定します。


    #アプリ名/views.py
    from django.views import generic
    from . import forms, models
    from django.urls import reverse_lazy

    class Reverse(generic.TemplateView):
        template_name = "アプリ名/reverse.html"
    class ToDoView(generic.edit.FormView):
        template_name = 'アプリ名/forms.html'
        form_class = forms.ToDoForm
        success_url = reverse_lazy('reverse')
        def form_valid(self, form):
            form.save()
            return super().form_valid(form)
    class ToDoList(generic.ListView):
        template_name = 'アプリ名/list.html'
        model = models.ToDoModel



ルーティング

 ルーティングを設定します。


    #アプリ名/urls.py
    from django.urls import path
    from . import views
    urlpatterns = [
        path('reverse/',views.Reverse.as_view(), name="reverse"),
        path('list/',views.ToDoList.as_view()),
        path('',views.ToDoView.as_view()),
    ]






それでは細かく見ていきたいと思います。

 django-admin startprojectでプロジェクトを作成した状態から始めます。

 ターミナルでpython manage.py runserverを実行して、ブラウザにlocalhost:8000で接続した状態と、ディレクトリの構成は以下の様になります。









アプリ作成

 ターミナルでpython manage.py startappでアプリを作成します。アプリの名前はアプリ名とします。プロジェクト名、アプリ名は日本語のままだと不都合が起きかねないので適切な名前で作成してください。公式チュートリアルではmysiteがプロジェクト名、pollsがアプリ名になります。




 プロジェクト名フォルダ内のsettings.pyのINSTALLED APPに「'アプリ名'」を登録し、urls.pyにアプリ名/urls.pyへのルーティングを記載します。





        #プロジェクト名/settings.py
        INSTALLED_APPS = [
            'django.contrib.admin',
            'django.contrib.auth',
            'django.contrib.contenttypes',
            'django.contrib.sessions',
            'django.contrib.messages',
            'django.contrib.staticfiles',
            'アプリ名',
        ]
    

        #プロジェクト名/urls.py
        from django.contrib import admin
        from django.urls import path, include
        
        urlpatterns = [
            path('', include('アプリ名.urls')),
            path('admin/', admin.site.urls),
        ]
    

 プロジェクト名フォルダ内での作業は以上です。

メニューに戻る


最初のテンプレートを作成

 アプリ名フォルダー内にtemplates/アプリ名/forms.htmlを作成します。VSCodeでは新しいファイル作成ボタンを押し「templates/アプリ名/forms.html」とすると一気に作成することができます。forms.htmlにはFormとだけ記載しておきましょう。後ほど内容は追加します。


メニューに戻る




最初のテンプレートをviews.pyに登録

 アプリ名フォルダー内のviews.pyを編集します。動作確認をしながら進めるので、TemplateViewから作成します。





        #アプリ名/views.py
        from django.views import generic
        class ToDoView(generic.TemplateView):
            template_name='アプリ名/forms.html'
    
メニューに戻る



urls.pyの作成

 アプリ名フォルダー内にurls.pyを作成し以下の様に記述します。





        #アプリ名/urls.py
        from django.urls import path
        from . import views
        urlpatterns = [
            path('',views.ToDoView.as_view()),
        ]
    
メニューに戻る



ブラウザで表示を確認

 ターミナルでpython manage.py runserverを実行しブラウザでlocalhost:8000に接続するとFormと表示されます。


メニューに戻る



models.pyでデータベースの設定

 models.pyを編集してデータベースに作成するテーブルを定義します。とりあえずtodoというフィールドを一つだけ作成して後ほど増やしていきます。


    #アプリ名/models.py
    from django.db import models

    class ToDoModel(models.Model):
        todo = models.CharField(max_length=20)

 models.pyのCharFieldではmax_lengthの指定が必須なります。最大文字数を20文字で設定します。

 ターミナルでpython manage.py makemigrationsを実行した後にpython manage.py migrateを実行するとdb.sqlite3にapp_todomodelというテーブルが作成されます。

 app_todomodelappというアプリのmodels.pyにclassで定義されているToDoModelという意味になります。

 VSCodeにSQLite Viewerがインストールされていれば以下のような画面で確認することができます。


メニューに戻る



forms.pyの作成

 アプリ名フォルダー内にforms.pyを作成し以下のように記述します。





    #アプリ名/forms.py
    from django import forms
    from . import models

    class ToDoForm(forms.Form):
        todo = forms.CharField()

 forms.pyのCharFieldではmax_lengthの指定は必要ありません。

メニューに戻る





views.pyにformsを読み込む

 アプリ名フォルダー内のviews.pyを編集します。


    #アプリ名/views.py
    from django.views import generic
    from . import forms

    class ToDoView(generic.edit.FormView):
        template_name='app/forms.html'
        form_class = forms.ToDoForm

 from . import formsでviews.pyと同じフォルダー内にあるforms.pyを呼び出します。TemplateViewで定義していたテンプレートをFormsViewに変更します。form_class = forms.ToDoFormでforms.pyに定義されているclassを呼び出します。

メニューに戻る





テンプレートにフォームを読み込む

 Formとだけ記載していたforms.pyからFormを消して以下のように編集します。


    #アプリ名/templates/アプリ名/forms.html
    <form method="post">
    {% csrf_token %}
    {{ form.as_p }}
    </form>

 formがpostで呼ばれた時の挙動を定義します。csrf_tokenはセキュリティ上必須となる項目で記載しなければエラーになります。

 その下の行で、先ほどforms.pyで定義したフォームを呼び出しています。.as_pはpタグで呼び出すという意味です。

メニューに戻る




ブラウザでフォームの確認

 ターミナルでpython manage.py runserverを実行するとブラウザにToDoのCharactor Fieldが表示されます。


メニューに戻る








フォームにボタンを追加

 さらにforms.htmlにButton要素を追加します。


    #アプリ名/templates/アプリ名/forms.html
    <form method="post">
    {% csrf_token %}
    {{ form.as_p }}
    </form>
    <button type="submit">送信</button>



メニューに戻る



データベースに保存する設定

 データベースに保存する設定を行います。

 ボタンをクリックしてデータの保存が成功した時に表示するページを作成します。forms.htmlと同じフォルダー内にreverse.htmlを作成し<h1>Reverse</h1>と記述します。






 reverse.htmlを表示するためにviews.pyとurls.pyを編集します。


    #アプリ名/views.py
    from django.views import generic
    from . import forms

    class Reverse(generic.TemplateView):
        template_name = "app/reverse.html"
    class ToDoView(generic.edit.FormView):
        template_name='app/forms.html'
        form_class = forms.ToDoForm



    #アプリ名/urls.py
    from django.urls import path
    from . import views

    urlpatterns = [
        path('reverse/',views.Reverse.as_view(), name="reverse"),
        path('',views.ToDoView.as_view()),
    ]

 これでreverse.htmlのルーティングができました。

 ToDoViewにはname属性をつけていませんでしたが、今回はname="reverse"を付けています。



 views.pyを編集してデータの保存が成功した時の挙動としてreverse.htmlを表示する設定をsuccess_urlに行います。success_urlでは先ほどurls.pyでつけたname属性のreverseを指定しています。 次にform_validメソッドでデータの保存を設定します。


    #アプリ名/views.py
    from django.views import generic
    from . import forms
    from django.urls import reverse_lazy

    class Reverse(generic.TemplateView):
        template_name = "app/reverse.html"
    class ToDoView(generic.edit.FormView):
        template_name='app/forms.html'
        form_class = forms.ToDoForm
        success_url = reverse_lazy('reverse')
        def form_valid(self, form):
            form.save()
            return super().form_valid(form)


 forms.pyを編集します。


    #アプリ名/forms.py
    from django import forms
    from . import models
    class ToDoForm(forms.Form):
        todo = forms.CharField()
        def save(self):
            data = self.cleaned_data
            models.ToDoModel(todo = data['todo']).save()

 この時、models.ToDoModel(todo = data['todo'])の一つ目のtodoはmodels.pyで定義したtodo、二つ目のdata['todo']のtodoがforms.pyのtodoを表します。したがって、models.pyのToDoModelクラスのtodoにforms.pyのtodoに入力された値を代入するという意味になります。






 Charactor Fieldに何かを入力してボタンをクリックします。






 SQLiteにデータが保存されていることが確認できます。


メニューに戻る



ListViewでデータベースの内容確認

 保存した内容をリストで確認できるようにしていきます。

 templates/アプリ名フォルダー内にlist.htmlを作成し、<h1>List</h1>と記載します。

 views.pyとurls.pyを以下のように編集します。


    #アプリ名/views.py
    from django.views import generic
    from . import forms, models
    from django.urls import reverse_lazy

    class Reverse(generic.TemplateView):
        template_name = "app/reverse.html"
    class ToDoView(generic.edit.FormView):
        template_name='app/forms.html'
        form_class = forms.ToDoForm
        success_url = reverse_lazy('reverse')
        def form_valid(self, form):
            form.save()
            return super().form_valid(form)
    class ToDoList(generic.ListView):
        template_name = 'app/list.html'
        model = models.ToDoModel



    #アプリ名/urls.py
    from django.urls import path
    from . import views

    urlpatterns = [
        path('reverse/',views.Reverse.as_view(), name="reverse"),
        path('list/',views.ToDoList.as_view()),
        path('',views.ToDoView.as_view()),
    ]

 先ほど作成しておいたlist.htmlを以下の様に編集します。


    #アプリ名/templates/アプリ名/list.html
    <h1>List</h1>
    {% for todo_list in object_list %}
    {{ todo_list.todo }}
    {% endfor %}

 ここでobject_listはDjangoで定義されている変数です。models.pyからhtmlにデータを渡すために使用します。object_listに渡されたQuerySetをfor文でtodo_listに代入して表示します。todo_listはこちらで定義した任意の変数になります。

 ターミナルでpython manage.py runserverを実行して localhost:8000/list/ に接続すると、データベースに保存されたデータを表示すことが出来ました。


メニューに戻る



データベースにフィールドを追加する

 model.pyのToDoModelクラスにtodoというフィールドを一つだけ定義していましたので増やしていきたいと思います。models.pyとforms.pyを以下のように編集します。


    #アプリ名/models.py
    from django.db import models

    class ToDoModel(models.Model):
        todo = models.CharField(max_length=20)
        detail = models.CharField(max_length=200)
        date = models.DateField()

 ターミナルでmakemigrationsを実行すると以下のようなメッセージが表示されます。


    #ターミナル
    It is impossible to add a non-nullable field 'date' to todomodel without specifying a default. This is because the database needs something to populate existing rows.
    Please select a fix:
    1) Provide a one-off default now (will be set on all existing rows with a null value for this column)
    2) Quit and manually define a default value in models.py.
    Select an option:



 ターミナルでmakemigrationsを実行すると以下のようなメッセージが表示されます。新たなフィールドを背ってした際に、既に存在するレコードのデータをどうするかを聞かれています。


    #ターミナル
    It is impossible to add a non-nullable field 'detail' to todomodel without specifying a default. This is because the database needs something to populate existing rows.
    Please select a fix:
    1) Provide a one-off default now (will be set on all existing rows with a null value for this column)
    2) Quit and manually define a default value in models.py.
    Select an option:

 Select an option:の後に 1 を入力して実行すると>>>と表示されます。今回は日付を聞かれていますので'2000-1-1'と入力して実行します。シングルクオーテーションマークは外さずにそのまま入力してください。するともう一度に下の様なメッセージが表示されて今度はdetailをどうするか聞かれています。同じくSelect an option:の後に 1 を入力して実行した後、''(シングルクオーテーションマーク2つ)と入力して実行します。


 ターミナルに以下のようなメッセージが表示されれば成功です。Migrations for 'app':の後にもう一行表示されるのですが、表示が異なるかもしれないのであえて省いています。


    #ターミナル
    Migrations for 'app':
    - Add field date to todomodel
    - Add field detail to todomodel



 次にターミナルでpython manage.py migrateを実行するとデータベース変更が適応されます。

 先ほどターミナルから追加したdateの2000-01-01とdetailの''で指定した空データが確認できます。



 forms.pyを編集します。


    #アプリ名/forms.py
    from django import forms
    from . import models
    class ToDoForm(forms.Form):
        todo = forms.CharField()
        detail = forms.CharField()
        date = forms.DateField()
        def save(self):
            data = self.cleaned_data
            models.ToDoModel(
                todo = data['todo'],
                detail = data['detail'],
                date = data['date']
            ).save()



 ブラウザでlocalhost:8000に接続すると以下のように入力欄が追加されています。




 次に何かを入力してからボタンをクリックします。

 日付の入力方法には注意してください。




 入力されたデータを確認することが出来ます。






 list.htmlを以下の様に編集します。


    #アプリ名/list.html
    <h1>List</h1>
    {% for todo_list in object_list %}
    {{ todo_list.todo }}
    {{ todo_list.detail }}
    {{ todo_list.date }}
    {% endfor %}



 ターミナルでpython manage.py runserverを実行してブラウザでlocalhost:8000/list/に接続するとデータベースに保存されたデータをListViewで確認できます。




 これでは分かりにくいのでテーブル形式で表示してみたいと思います。

 list.htmlを以下の様に編集します。


    #アプリ名/list.html

    <h1>List</h1>
    <table>
        <thead>
            <tr><th>todo</th><th>date</th><th>detail</th></tr>
        </thead>
        <tbody>
            {% for todo_list in object_list %}
            <tr>
                <td>
                    {{ todo_list.todo }}
                </td>
                <td>
                    {{ todo_list.date }}
                </td>
                <td>
                    **{{ todo_list.detail }}**
                </td>
            </tr>
            {% endfor %}
        </tbody>
    </table>




 ブラウザでlocalhost:8000/listでアクセスすると以下の様に表示されます。

 少しだけ見やすくなりましたかね?

メニューに戻る




 以上、FormViewからデータをデータベースに保存してみましたので投稿いたしました。





 分かりにくい部分などありましたらお知らせ頂けると助かります。お知らせいただいた内容は公開されることはありません。どの記事から投稿されたかは分かるようにしています。

お名前:

メールアドレス:








>