This article is a Private article. Only a writer and users who know the URL can access it.
Please change open range to public in publish setting if you want to share this article with other users.

More than 3 years have passed since last update.

[Day 16]FormとModelForm

Last updated at Posted at 2021-01-21

January 21, 2021
←前回:Day 15 簡単なトピック投稿画面の作成

「Djangoを学びたい」とのことでありましたら[Day 1]Djangoの開発環境から読むことをおすすめします。

はじめに

前回までフォームはModelFormを扱ってきました。今回は次の説明のためにもFormとModelFormについて触れておきます。

Formを使う

これまでDjangoにおけるフォームの役割を説明してきました。
このフォーム機能を具現化するクラスがFormクラスです。
Djangoの便利さを体感するためModelFormから見てきましたが、ModelFormはFormを継承したクラスです。
ではTopicModelFormをFormを継承したクラスで書き直して見ましょう。

thread/forms.py

from django import forms
from . models import Topic, Category

class TopicModelForm(forms.ModelForm):
    class Meta:
        model=Topic
        fields=[
            'title',
            'user_name',
            'category',
            'message',
        ]

    def __init__(self, *args, **kwargs):
        # kwargs.setdefault('label_suffix', '')
        super().__init__(*args, **kwargs)
        self.fields['category'].empty_label = '選択して下さい'
        self.fields['user_name'].widget.attrs['value'] = '匿名'
        # self.fields['title'].widget.attrs['class'] = 'huga'

class TopicForm(forms.Form):
    title = forms.CharField(
        label='タイトル',
        max_length=255,
        required=True,
    )
    user_name = forms.CharField(
        label='お名前',
        max_length=30,
        required=True,
        widget=forms.TextInput(attrs={'value': '名無し'}),
    )
    category = forms.ModelChoiceField(
        label='カテゴリー',
        queryset=Category.objects.all(),
        required=True,
        empty_label='選択して下さい',
    )
    message = forms.CharField(
        label='本文',
        widget=forms.Textarea,
        required=True,
    )

次に先程作成したTopicFormを使ってトピックを作成する関数を見てみましょう。
処理をわかりやすくするためクラスベースビューでなく敢えてtopic_create関数に変更を加えています。
TopicFormのインポートを忘れないようにしてくださいね。

thread/views.py

from . forms import TopicModelForm, TopicForm

def topic_create(request):
    template_name = 'thread/create_topic.html'
    ctx = {}
    if request.method == 'GET':
       form = TopicForm()
       ctx['form'] = form
        return render(request, template_name, ctx)

    if request.method == 'POST':
        topic_form = TopicForm(request.POST)
        if topic_form.is_valid():
            # topic_form.save()
           topic = Topic()
           cleaned_data = topic_form.cleaned_data
           topic.title = cleaned_data['title']
           topic.message = cleaned_data['message']
           topic.user_name = cleaned_data['user_name']
           topic.category = cleaned_data['category']
           topic.save()            
            return redirect(reverse_lazy('base:top'))
        else:
            ctx['form'] = topic_form
            return render(request, template_name, ctx)

class TopicDetailView(DetailView):
    template_name = 'thread/detail_topic.html'
    model = Topic
    context_object_name = 'topic'

views.pyのcreate_topic関数を使うようにurls.pyも変更しておきましょう。

thread/urls.py

from django.urls import path

from . import views
app_name = 'thread'

urlpatterns = [
    path('<int:pk>/', views.TopicDetailView.as_view(), name='topic'),
    # path('create_topic/', views.TopicFormView.as_view(), name='create_topic'),
    path('create_topic/', views.topic_create, name='create_topic'),
]

前回説明を省きましたが、formのコンストラクタにrequest.POSTを入れることでデータと紐付けられたフォームが出来ます。
このフォームはis_valid()関数を呼ぶことでデータの検証を行えます。

Topicオブジェクトの保存の仕方が変更されたことにお気づきでしょうか?Formの場合はis_valid()関数が呼ばれた後にcleaned_dataから検証済みのデータを取り出し、Modelのオブジェクトに値をセットしてオブジェクトのsave()関数を呼び保存します。
尚、データの検証はFormに書かれた各フィールドの内容に応じてチェックされます。

一方、ModelFormはis_valid()関数が呼ばれるとModelに記載されたルールでデータ検証が行われます。
そして保存する場合はModelForm自体が備えているsave()を呼び出して保存をします。
ここが大きな違いですね。

セレクトタグの未選択の文言やValueの初期値を与える

カテゴリーはセレクトタグを使用していますが、(正確にはデフォルトのwidgetがselectという意味)このセレクトタグの未選択状態では「——–」というように点線で表現されています。
この文言を変更したいという需要は多いと思います。
Formの場合は以下のようにempty_labelを指定するだけです。
また、入力値の初期値を与えるなどタグの要素を変更する場合は以下のようにします。

thread/forms.py

    user_name = forms.CharField(
        label='お名前',
        max_length=50,
        required=True,
+       widget=forms.TextInput(attrs={'value': '名無し'}),
    )
    category = forms.ModelChoiceField(
        label='カテゴリー',
        queryset=Category.objects.all(),
        required=True,
+       empty_label='選択して下さい',

確認してみましょう。以下のように変更されていればOKです。
image.png

ではModelFormの場合はどうすれば良いのでしょうか?ModelFormの場合は以下のようにします。

thread/forms.py

class TopicModelForm(forms.ModelForm):
    class Meta:
        model=Topic
        fields=[
            'title',
            'user_name',
            'category',
            'message',
        ]  

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields['category'].empty_label = '選択して下さい'
        self.fields['user_name'].widget.attrs['value'] = '名無し'

ModelFormの場合、プロパティを直接変更できないため、init関数をオーバライドして対応します。
この方法はインプットタグのclass要素を修正したい場合にも使えます。
また、ModelFormでwidgetを変更する場合には以下のように変更することが出来ます。
widgetの指定をしながらタグの要素を指定するのです。

thread/forms.py

class TopicModelForm(forms.ModelForm):
    class Meta:
        model=Topic
        fields=[
            'title',
            'user_name',
            'category',
            'message',
        ]
+      widgets = {
+          'title' : forms.TextInput(attrs={'class': 'hoge'}),
+           'user_name' : forms.TextInput(attrs={'value': '名無し'}),
+       }

    def __init__(self, *args, **kwargs):
        # kwargs.setdefault('label_suffix', '')
        super().__init__(*args, **kwargs)
        self.fields['category'].empty_label = '選択して下さい'

これでレンダリングされた’title’のinputタグには’hoge’クラスが付与されています。
クラスだけではなくattrsを用いるとタグの様々な属性について操作をすることが出来ます。

label_suffixを変更する

label_suffixとはラベルの末尾につく記号でデフォルトではコロンがついています。
たとえば「タイトル:」や「お名前:」のように全てコロンが付いていますよね。
場合によってはこれを変更したい、或いは削除したいということもあると思います。
そういう場合にlabel_suffixを設定します。フォーム生成時にコンストラクタに渡す方法もあるのですが、init関数をオーバライドしてlabel_suffixを設定する方法が簡単で良いと思います。

thread/form.py

class TopicModelForm(forms.ModelForm):
    class Meta:
        model=Topic
        fields=[
            'title',
            'user_name',
            'category',
            'message',
        ]  

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields['category'].empty_label = '選択して下さい'
        self.fields['user_name'].widget.attrs['value'] = '名無し'

class TopicForm(forms.Form):
    label_suffix = ''
    title = forms.CharField(
        label='タイトル',
        max_length=255,
        required=True,
    )
    user_name = forms.CharField(
        label='お名前',
        max_length=30,
        required=True,
        widget=forms.TextInput(attrs={'value': '名無し'}),
    )
    category = forms.ModelChoiceField(
        label='カテゴリー',
        queryset=Category.objects.all(),
        required=True,
        empty_label='選択して下さい',
    )
    message = forms.CharField(
        label='本文',
        widget=forms.Textarea,
        required=True,
    )

    def __init__(self, *args, **kwargs):
        kwargs.setdefault('label_suffix', '')
        super().__init__(*args, **kwargs)

これでラベルのコロンが消えました。
もちろん、矢印等にカスタムすることも可能です。
公式ドキュメントではフォームAPIで扱っています。

終わりに

やはりDjangoは最初に覚えることが多いようですね。
これに慣れることができたら作業がスムーズに進むと思います。
仮に今回の作成物が完成したとしても「Djangoになれた」とは到底言えなさそうです。
しかし、着実に成長している実感があるので今やることをやり、次に繋げたいと考えています。

それではまたまた

←前回:Day 15 簡単なトピック投稿画面の作成
→次回:Day 17 FromとHTMLのレンダリングの関係を理解する

0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up