Python Django入門 初めの1歩からWEBアプリ作成までの流れ その10 確認画面(プレビュー)表示後にDB登録)

今回の内容はこちらです。

今回の内容

①入力フォームから値を入力する。

②入力値を確認画面に表示させる。

③確認画面で表示された内容に誤りが無ければDBに保存する。

      1. ①入力フォームから値を入力する。
      2. ②入力値を確認画面に表示させる。
      3. ③確認画面で表示された内容に誤りが無ければDBに保存する。
  1. models.py
  2. forms.py
    1. forms.pyの重要ポイント:forms.Formを継承
  3. views.py
    1. views.pyの重要ポイント-1:フォーム入力値の引き継ぎは、contextに辞書型でformを直接引き渡す
    2. views.pyの重要ポイント-2:各VIEWはFormViewを継承
    3. views.pyの重要ポイント-3:DBへの保存にはcreate()メソッドを使う
  4. pj/urls.py
  5. app/urls.py
    1. app/urls.pyの重要ポイント:各テンプレート毎にURLとVIEWを作成
  6. pj/templates/app/base.html
  7. フォーム入力画面:pj/templates/app/price-form.html
    1. フォーム入力画面の重要ポイント:<form>タグのaction属性を{%url%}タグで指定
  8. 確認画面:pj/templates/app/price-form-confirm.html
    1. 確認画面の重要ポイント-1:前の画面で入力したフォームを表示
    2. 確認画面の重要ポイント-2:前の画面と次の画面に遷移できるようにする
      1. 前の画面に戻る:formタグで、postメソッドで前の画面に戻れるようにすることで値を前の画面に引き継いだ状態で戻ることが出来る。
      2. 次の画面に進む:次の画面の画面のURLをactionの属性値に設定することでformの内容を次の画面に引き継ぐことが出来る。
    3. 確認画面の重要ポイント-3:次の画面にフォーム入力値を引き継ぐ
  9. 入力完了画面:pj/templates/app/price-form-confirm.html
  10. admin.py
  11. settings.py

models.py

models.py
from django.db import models
# Create your models here.

class PriceFormData(models.Model):
    code = models.CharField(verbose_name="ティッカーシンボル", max_length=50)
    start = models.DateField(verbose_name="開始日")
    end = models.DateField(verbose_name="終了日")

forms.py

forms.pyの重要ポイント:forms.Formを継承

以前の記事でforms.Formとforms.ModelFormを継承するパターンのそれぞれについて解説しました。

forms.ModelFormは、フォーム入力が完了すると自動的にDBに保存されます。これでは、今回目的とする確認画面が表示される前に既にDBに登録されてしまうことになるため、ModelFormは使用できません。

forms.py
from django.db import models
import datetime
today = datetime.date.today().strftime('%Y-%m-%d')

# Create your models here.

class PriceForm(forms.Form):
    code = forms.CharField(label='ティッカーシンボル', initial='SP500:^GSPC, トヨタ自動車:7203.T', max_length=50,widget=forms.TextInput(attrs={'class':'form-control'}))
    start = forms.DateField(label='開始日', initial='2015-01-01', input_formats=['%Y-%m-%d'],widget=forms.DateInput(attrs={'class':'form-control'}))
    end = forms.DateField(label='終了日', initial=today, input_formats=['%Y-%m-%d'],widget=forms.DateInput(attrs={'class':'form-control'}))

views.py

views.pyの重要ポイント-1:フォーム入力値の引き継ぎは、contextに辞書型でformを直接引き渡す

フォーム入力値を使用して以後の処理に使用する場合、多くのケースで「request.POST.get(‘キー’)」を使ってフォーム入力値を取得します。

ですが、今回は確認画面に対して、フォームで入力された全フィールドの「キー」と「入力値」を引き渡す必要があるため、formを丸ごと格納してしまった方が効率が良いです。

尚、render()メソッドのcontextには、下記のように辞書型で直接formを格納するパターンも、変数context={‘form’ : form}を定義してcontextをrender()に引き渡してもどちらでも大丈夫です。

views.pyの重要ポイント-1:フォーム入力値の引き継ぎは、contextに辞書型でformを直接引き渡す
def form_valid(self, form):
        return render(self.request, 'app/price-form.html', {'form':form})

views.pyの重要ポイント-2:各VIEWはFormViewを継承

app/urls.pyの部分で解説致しますが、今回のアプリに使用するテンプレートは全てフォームから値を継承しますのでFormViewを継承してクラスを作成します。

views.pyの重要ポイント-3:DBへの保存にはcreate()メソッドを使う

また、DBへの保存は、PriceCompleteViewクラスのform_valid()メソッド内でcreate()メソッドを使用して保存致します。

create()メソッドは、models.モデル.objects.create(キー = 値)の形式で使用することで、クラスを使用しない場合でもDBへ保存することが出来るメソッドです。

views.py(create()メソッド部分抜粋)
class PriceCompleteView(FormView):
    template_name = 'app/price-form-complete.html'
    form_class = forms.PriceForm

    def form_valid(self,form):
        context = {'form':form,}
        models.PriceFormData.objects.create(**form.cleaned_data) # <= create()メソッドを使いDBを更新致します。
        return render(self.request, 'app/price-form-complete.html', context)
views.py
from django.shortcuts import render
from django.views import View
from django.views.generic import TemplateView
from . import forms
from . import models

# Create your views here.

class PriceFormView(FormView):
    form_class = forms.PriceForm
    template_name = "app/price-form.html"
    # success_url = 'confirm/'
    def form_valid(self, form):
        return render(self.request, 'app/price-form.html', {'form':form})

class PriceConfirmView(FormView):
    form_class = forms.PriceForm
    template_name = "app/price-form-confirm.html"

    def form_valid(self,form):
        context = {'form':form,}
        return render(self.request, 'app/price-form-confirm.html', context)

class PriceCompleteView(FormView):
    template_name = 'app/price-form-complete.html'
    form_class = forms.PriceForm

    def form_valid(self,form):
        context = {'form':form,}
        models.PriceFormData.objects.create(**form.cleaned_data)
        return render(self.request, 'app/price-form-complete.html', context)

pj/urls.py

pj/urls.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('', include('app.urls')),
    path('admin/', admin.site.urls),
]

app/urls.py

app/urls.pyの重要ポイント:各テンプレート毎にURLとVIEWを作成

今回のアプリは、①「フォーム入力画面」、②「確認画面」、③「完了画面」の3パターンのテンプレートを使用します。①、②、③それぞれにURLとVIEWを設定します。

app/urls.py
from django.contrib import admin
from django.urls import path
from . import views

urlpatterns = [
    path('', views.PriceFormView.as_view(), name='app/price-form'),
    path('confirm/', views.PriceConfirmView.as_view(), name='app/price-form-confirm'),
    path('complete/', views.PriceCompleteView.as_view(), name='app/price-form-complete'),
    path('admin/', admin.site.urls),
]

pj/templates/app/base.html

pj/templates/app/base.html
{% load static %}
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>確認画面表示後にDBに登録するDjangoフォーム</title>
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
    <script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>
    <link rel="stylesheet" href="{% static 'app/style.css' %}">
</head>
<body>
    {% block main %}
    {% endblock %}>
</body>
</html>

フォーム入力画面:pj/templates/app/price-form.html

フォーム入力画面の重要ポイント:<form>タグのaction属性を{%url%}タグで指定

{% url %}タグで確認画面のURLを指定することで、フォーム入力値が確認画面に引き継がれます

pj/templates/app/price-form.html
{% extends 'app/base.html' %}
{% block main %}
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
    <a href="/" class="navbar-brand">株価条件入力フォーム</a>
</nav>
<div class="container mt-4">
    <form method="post" action="{% url 'app/price-form-confirm' %}">
        {% csrf_token %}
        {{ form.as_p }}
        <input type="submit" value="取得予定の株価の条件をDBに保存する" class="btn btn-info">
    </form>
</div>
{% endblock %}

確認画面:pj/templates/app/price-form-confirm.html

確認画面の重要ポイント-1:前の画面で入力したフォームを表示

前の画面(フォーム入力画面)で入力したフォーム入力値を{% for %}タグで回して取り出します。

確認画面の重要ポイント-1:前の画面で入力したフォームを表示
    {% for field in form %}
        <li><h3><label for="{{ field.id_for_label }}" style="border-bottom: 1px solid;">{{ field.label_tag }}</label></h3></li>
        <h6>{{ field.value }}</h6><br>
    {% endfor %}

確認画面の重要ポイント-2:前の画面と次の画面に遷移できるようにする

前の画面に戻る:formタグで、postメソッドで前の画面に戻れるようにすることで値を前の画面に引き継いだ状態で戻ることが出来る。

確認画面の重要ポイント-2:前の画面に遷移できるようにする
    <form action="{% url 'app/price-form' %}" method="POST">
        {% csrf_token %}
        <button type="submit" class="btn btn-primary btn-lg">戻る</button>
        {% for field in form %}{{ field.as_hidden }}{% endfor %}
    </form>

次の画面に進む:次の画面の画面のURLをactionの属性値に設定することでformの内容を次の画面に引き継ぐことが出来る。

確認画面の重要ポイント-2:次の画面に遷移できるようにする
    <form action="{% url 'app/price-form-complete' %}" method="POST" >
        {% csrf_token %}
        {% for field in form %}
        <input type="hidden" name="{{ field.name }}" value="{{ field.value }}"><br>
        {% endfor %}
        <input type="submit" value="送信" class="btn btn-primary btn-lg">
    </form>

確認画面の重要ポイント-3:次の画面にフォーム入力値を引き継ぐ

ポイント-1と同様に、前の画面(フォーム入力画面)で入力したフォーム入力値を{% for %}タグで回して取り出します。

ここでは、<input type=”hidden”>とすることで非表示にしています。

{% for field in form %}<input>{% endfor %}の部分は、{{ forms.as_p }}としても構いません。但しその場合、上記で表示していた部分と重複してしまいますのでご留意下さい。

また、管理人はこの部分で、前の画面のフォーム入力内容をうまく表示が出来ておらず、次の画面にフォームを引き渡すことが出来ずかなり時間を浪費しましたので、この部分で前の画面から受け取ったフォームをしっかり表示させるようにご注意ください。

確認画面の重要ポイント-3:次の画面にフォーム入力値を引き継ぐ
    <form action="{% url 'app/price-form-complete' %}" method="POST" >
        {% csrf_token %}
        {% for field in form %}
        <input type="hidden" name="{{ field.name }}" value="{{ field.value }}"><br>
        {% endfor %}
        <input type="submit" value="送信" class="btn btn-primary btn-lg">
    </form>
pj/templates/app/price-form-confirm.html
{% extends 'app/base.html' %}
{% block main %}
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
    <a href="/" class="navbar-brand">株価入力フォーム 確認画面</a>
</nav>
<div class="container mt-4">
    <h2>下記内容でお間違いないでしょうか?お間違いなければ下記の送信ボタンを押下頂きますとデータベースに登録出来ます。</h2>
    <ul>
    {% for field in form %}
        <li><h3><label for="{{ field.id_for_label }}" style="border-bottom: 1px solid;">{{ field.label_tag }}</label></h3></li>
        <h6>{{ field.value }}</h6><br>
    {% endfor %}
    </ul>
    <form action="{% url 'app/price-form' %}" method="POST">
        {% csrf_token %}
        <button type="submit" class="btn btn-primary btn-lg">戻る</button>
        {% for field in form %}{{ field.as_hidden }}{% endfor %}
    </form>
    <hr>
    <form action="{% url 'app/price-form-complete' %}" method="POST" >
        {% csrf_token %}
        {% for field in form %}
        <input type="hidden" name="{{ field.name }}" value="{{ field.value }}"><br>
        {% endfor %}
        <input type="submit" value="送信" class="btn btn-primary btn-lg">
    </form>
</div>
{% endblock %}

入力完了画面:pj/templates/app/price-form-confirm.html

確認画面からしっかりフォームが引き渡されているか確認するためにこの画面でフォームを表示するようにしています。

入力完了画面:pj/templates/app/price-form-confirm.html
{% extends 'app/base.html' %}
{% block main %}
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
    <a href="/" class="navbar-brand">株価入力フォーム 入力完了画面</a>
</nav>
<div class="container mt-4">
    <h2>入力された内容でデータベースを更新しました。</h2>
    {% for field in form %}
    {{ field.id_for_label }} : {{ field.value }}<br>
    {% endfor %}
    <a href="{% url 'app/price-form' %}">トップページへ戻る</a><br>
</div>
{% endblock %}

admin.py

admin.py
from django.contrib import admin
from .models import *
# Register your models here.
admin.site.register(Price)
admin.site.register(PriceFormData)

settings.py

settings.py
"""
Django settings for pj project.

Generated by 'django-admin startproject' using Django 4.1.7.

For more information on this file, see

Django
The web framework for perfectionists with deadlines.
For the full list of settings and their values, see
Django
The web framework for perfectionists with deadlines.
""" from pathlib import Path # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/4.1/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! SECRET_KEY = 'django-insecure-_r9x-f_kr#@vek*eccnzfq4$9kl5227!p@hv7=a@)&$)7yica#' # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True ALLOWED_HOSTS = [] # Application definition INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'app', ] MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] ROOT_URLCONF = 'pj.urls' TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [BASE_DIR, 'templates'], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, ] WSGI_APPLICATION = 'pj.wsgi.application' # Database # https://docs.djangoproject.com/en/4.1/ref/settings/#databases DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': BASE_DIR / 'db.sqlite3', } } # Password validation # https://docs.djangoproject.com/en/4.1/ref/settings/#auth-password-validators AUTH_PASSWORD_VALIDATORS = [ { 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', }, { 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', }, { 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', }, { 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', }, ] # Internationalization # https://docs.djangoproject.com/en/4.1/topics/i18n/ LANGUAGE_CODE = 'en-us' TIME_ZONE = 'UTC' USE_I18N = True USE_TZ = True # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/4.1/howto/static-files/ STATIC_URL = 'static/' import os STATICFILES_DIRS = ( os.path.join(BASE_DIR, 'static/'), ) # Default primary key field type # https://docs.djangoproject.com/en/4.1/ref/settings/#default-auto-field DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'

コメント

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