naritoブログ

このブログはPython/DjangoとBootstrap4で作成されました
Githubのソースコード

Djangoで、フォームの内容を保持する(セッション編)

約27日前 2018年5月1日2:35
プログラミング関連
Django Python

概要


Djangoで、ユーザー情報の入力後に確認画面を表示したいと思います。

ユーザー情報が入ったPOSTデータをセッションに保存する方法を使いますが、中々に便利です。

見た目


トップページは、ユーザーの一覧が表示されています。
トップページの様子。ユーザーの一覧が表示される

ユーザー情報入力画面で入力し、送信を押すと
ユーザー情報の入力画面

確認画面で、内容を確認できます。
送信を押すと確認画面へ。さきほど入力した内容が移されている

戻るで入力画面に戻ると、ユーザー名は既に入力済みの状態です。画像のように、パスワードも入力済みにして戻らせることもできます。
戻るを押したら、ちゃんと入力済みの状態で戻れます

送信では、データが正しく追加されます。
送信すると、正しく追加されます

ソースコード



urls.py


from django.urls import path
from . import views

app_name = 'register'

urlpatterns = [
    path('', views.UserList.as_view(), name='user_list'),
    path('user_data_input/', views.UserDataInput.as_view(), name='user_data_input'),
    path('user_data_confirm/', views.UserDataConfirm.as_view(), name='user_data_confirm'),
    path('user_data_create/', views.UserDataCreate.as_view(), name='user_data_create'),
]


forms.py


UserCreationFormを継承した、ユーザー作成用の一般的で、汎用的なフォームです。Bootstrap4対応しています。
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth import get_user_model

User = get_user_model()  # Userモデルの柔軟な取得方法


class UserCreateForm(UserCreationForm):
    """ユーザー登録用フォーム"""

    class Meta:
        model = User
        fields = (User.USERNAME_FIELD,)  # ユーザー名として扱っているフィールドだけ、作成時に入力する

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        for field in self.fields.values():
            field.widget.attrs['class'] = 'form-control'



views.py


解説は、コメントのとおりです。
from django.contrib.auth import get_user_model
from django.urls import reverse_lazy
from django.views import generic
from .forms import UserCreateForm

User = get_user_model()


class UserList(generic.ListView):
    template_name = 'register/user_list.html'  # デフォルトUserだと、authアプリケーションのuser_list.htmlを探すため、明示的に書いておく
    model = User


class UserDataInput(generic.FormView):
    """ユーザー情報の入力"""
    template_name = 'register/user_data_input.html'
    form_class = UserCreateForm
    success_url = reverse_lazy('register:user_data_confirm')

    def get_form(self, form_class=None):
        # user_data_input.hmltで、データを送信した場合はここ
        if 'username' in self.request.POST:
            form_data = self.request.POST

        # 確認画面(user_data_confirm.html)から戻るリンクを押した場合や
        # 初回の入力欄表示はここ。セッションにユーザーデータがあれば、それをフォームに束縛させる
        else:
            form_data = self.request.session.get('form_data', None)

        return self.form_class(form_data)

    def form_valid(self, form):
        # 入力した値を、セッションに保存しておきます
        self.request.session['form_data'] = self.request.POST
        return super().form_valid(form)


class UserDataConfirm(generic.TemplateView):
    """ユーザー情報の確認"""
    template_name = 'register/user_data_confirm.html'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        form_data = self.request.session.get('form_data', None)
        context['form'] = UserCreateForm(form_data)
        return context


class UserDataCreate(generic.CreateView):
    """ユーザーの作成"""
    form_class = UserCreateForm
    success_url = reverse_lazy('register:user_list')

    def get_form(self, form_class=None):
        # popで、セッションに入れたユーザーデータ自体を取り出す
        # これをしないと、入力画面へ行くとデータが入力済みになり、ちょっと気持ちわるいです。
        form_data = self.request.session.pop('form_data', None)
        return self.form_class(form_data)




ここは本来、フォームオブジェクトにする必要はない(テンプレートへセッションから復元したrequest.POSTをそのまま渡せる)のですが、Contextバージョンと似た形にするため、フォームにしています。
class UserDataConfirm(generic.TemplateView):
    """ユーザー情報の確認"""
    template_name = 'register/user_data_confirm.html'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        form_data = self.request.session.get('form_data', None)
        context['form'] = UserCreateForm(form_data)
        return context



base.html


Bootstrap4を使った、よくあるbase.html
<!doctype html>
<html lang="ja">
<head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css"
          integrity="sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4" crossorigin="anonymous">

    <title>会員登録サンプル</title>
</head>
<body>

    <div class="container mt-3">
        {% block content %}{% endblock %}
    </div>

    <!-- Optional JavaScript -->
    <!-- jQuery first, then Popper.js, then Bootstrap JS -->
    <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"
            integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo"
            crossorigin="anonymous"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.0/umd/popper.min.js"
            integrity="sha384-cs/chFZiN24E4KMATLdqdvsezGxaGsi4hLGOzlXwp5UZB1LY//20VyM2taTB4QvJ"
            crossorigin="anonymous"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/js/bootstrap.min.js"
            integrity="sha384-uefMccjFJAIv6A+rW+L4AHf99KvxDjWSu1z9VI8SKNVmz4sk7buKt/6v9KI65qnm"
            crossorigin="anonymous"></script>
</body>
</html>



user_list.html


ユーザー作成ページへのリンクと、ユーザーの一覧が表示されるページ。
{% extends "register/base.html" %}
{% block content %}
<p><a href="{% url 'register:user_data_input' %}">ユーザー作成</a></p><hr>
{% for user in user_list %}
    <p>ユーザー名: {{ user.username }}</p><hr>
{% endfor %}
{% endblock %}



user_data_input.html


ユーザー情報の入力ページ
{% extends "register/base.html" %}
{% block content %}
<form action="" method="POST">
    {{ form.non_field_errors }}
    {% for field in form %}
    <div class="form-group">
        <label for="{{ field.id_for_label }}">{{ field.label_tag }}</label>
        {{ field }}
        {{ field.errors }}
    </div>
    {% endfor %}
    {% csrf_token %}
    <button type="submit" class="btn btn-primary btn-lg">送信</button>
</form>
{% endblock %}



user_data_confirm.html


確認ページです。
{% extends "register/base.html" %}
{% block content %}
    {% for field in form %}
    <div class="form-group">
        <label for="{{ field.id_for_label }}">{{ field.label_tag }}</label>
        {{ field.value }}
    </div>
    {% endfor %}

    <a href="{% url 'register:user_data_input' %}" class="btn btn-primary btn-lg">戻る</a>
    <hr>
    <form action="{% url 'register:user_data_create' %}" method="POST">
        <button type="submit" class="btn btn-primary btn-lg">送信</button>
        {% csrf_token %}
    </form>
{% endblock %}



ここは、入力された情報を表示するための部分です。{{ field.value }}で、値やテキストだけ取り出すことができます(input要素は生成しない)。生のパスワードが表示されて気持ち悪い場合は、各フィールドを直接書いていくとよいでしょう。
    {% for field in form %}
    <div class="form-group">
        <label for="{{ field.id_for_label }}">{{ field.label_tag }}</label>
        {{ field.value }}
    </div>
    {% endfor %}



以下は戻るボタンです。入力内容はセッションに保存済みなので、単純にアンカータグで戻れます(UserDataInputビューは、セッションにユーザーデータがあればそれをフォームに束縛する)
 <a href="{% url 'register:user_data_input' %}" class="btn btn-primary btn-lg">戻る</a>


送信はPOSTで送信しますが、入力内容をセッションに保存しているので、入力欄などは特に必要ありません。 {% csrf_token %}とsubmitなボタンだけで大丈夫です。
    <form action="{% url 'register:user_data_create' %}" method="POST">
        <button type="submit" class="btn btn-primary btn-lg">送信</button>
        {% csrf_token %}
    </form>



戻った際に、パスワードも入力済みにしたい場合


forms.pyの__init__内を、以下のようします。widget.render_value = Trueで、input type="password"の場合でも入力欄がクリアされません。
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields['password1'].widget.render_value = True
        self.fields['password2'].widget.render_value = True
        for field in self.fields.values():
            field.widget.attrs['class'] = 'form-control'