diadia

興味があることをやってみる。自分のメモを残しておきます。

Djangoのformをより良く見せるために(改訂版)

Djangoにおいてformウィジェットの理解を深めない限り、forms.ModelFormやforms.Formの利用したとて、フォームを使った画面が残念な結果になってしまう。

見た目の良いフォーム画面を作るには

これらの方法が考えられるがメリット、デメリットが存在する。 これらのメリット・デメリットを考えてみたい。

そもそもウィジェットとは?

現在自分の中ではウィジェットとはformだったりボタン等のユーザーインターフェースのことを指すと考えている。

htmlファイルを自ら作成して好みのformを作成する方法

これはテンプレートにformタグと共にインプットタグ等を書いていく方法で、class属性を思うままに書くことができるので自由に作ることができる。
しかしながらdjangoに備わっているインプットデータのバリデーション機能を享受できなくなるデメリットがある。自分でバリデーション機能も実装するのは手間なので避けるべき方法だと思う。

ModelFormやFormのウィジェットをforms.pyにて編集する方法

関連ドキュメント

https://docs.djangoproject.com/ja/2.2/topics/forms/
https://docs.djangoproject.com/ja/2.2/ref/forms/widgets/

例えばBootstrap4のform-controlを反映させたい場合以下のようになる。

class CommentModelForm(forms.ModelForm):
    class Meta:
        model = Comment
        fields = ("name","url","comment")
        
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        for field in self.fields.values():
            self.field["name"].widget.attrs["class"] = "form-control"
            self.field["url"].widget.attrs["class"] = "form-control"
            self.field["comment"].widget.attrs["class"] = "form-control"
    

その他ウィジェットを編集する方法は以下のようになる。なお、参考資料は以下である。 https://docs.djangoproject.com/ja/2.2/ref/forms/api/#accessing-the-fields-from-the-form

def __init__(self, *args, **kwargs):
   super().__init__(*args, **kwargs)
   for field in self.fields.values():
        self.field["name"].required = True  #必須項目のフォームに変更する
        self.field["url"].label = "URL"  #ユーザ視点でフォームの入力名が変わる。
        self.field["comment"].widget.attrs["placeholder"] = "コメントを書いてください。"

forms.pyのinitwidgetを編集する方法である。これを使うとformのwidgetの属性値を管理するのが楽になる。forms.pyを直接いじれない場面に遭遇するといじるために手間がかかる。またファイルは違えどviews.pyで編集する方法もこの方法に含まれる。

Javascriptを使ってformを改善させる方法

上記の方法はforms.pyにおいてformのインスタンス初期化時に属性をセットする方法である。 サードパーティのライブラリを使う場合に問題が顕在化する。例えばdjango-allauthではログイン等の画面も準備してくれるが、そのフォームを編集する際にforms.pyをいじることが多少難しくなることだ。実際いじることはできるが少し複雑になってしまうので構成を共有して理解するには難しいと思われる。
こちらの記事の一部にformの編集の仕方を書いている。
django-allauth : テンプレートのカスタマイズ - diadia

このような場合にはjavascriptを使ってDOMを操作することが良い思う。 共通のformを利用する場合にはincludeを挿入すればよいだろうし、コンポーネント化できるので積極的に使って良い方法だと思われる。問題点はjavascriptの知識が要求されることである。
Vue.jsとか他のフレームワークを使っている場合DOM操作がバッティングする恐れがあり、それらがどのように反応するかは検証する必要がある。自分の場合それがどのように反応するかは分かっていないのでそこをはっきりさせるのにコストがかかる。

window.addEventListener('load', function() {
    var email_form = document.getElementById("id_email")
    email_form.setAttribute('class', 'form-control');
})

サードパーティライブラリを使用する方法

django-widget-tweaksというライブラリが存在する。
https://python.keicode.com/django/form-add-class.php これを使えばテンプレートタグに追加したい属性値を直接記述できるようになる。サードパーティライブラリなのでメンテナンスされるかが外部に依存する事になってしまう。

{% load widget_tweaks %}
{{form.username|add_class:"form-control"}}

結論

個人的にはforms.pyのインスタンスの初期化で各ウィジェットを調整し、それが難しい場合にはjavascriptを使って調整する方法を取るのが望ましいと思う。