ModelForm でフィールド指定する際は fields 属性を使う

概要

実装方法は、大抵の場合1つとは限らない。
選択肢が複数あるときは、仕様を満たせるだけでなくセキュリティも考慮した選択ができるようになりたいものだ。
今回は、Django の ModelForm を使った実装で、イマイチな選択をしてしまった例を振り返りたい。

まえおき

ここに登場するコードは以下で実装されたものを前提としている。

画面やコードはサンプル向けにアレンジしたもののため、違和感はご愛嬌。

やりたかったこと

ModelForm を使い、モデルに定義されている特定のフィールドだけを描画したフォーム画面を作成したかった。
たとえば、書籍を登録する画面で、Book モデルに定義した特定のフィールドのみを表示し、登録ボタンを押したら入力値のバリデーションを行うようなフォームである。


class BookModel(models.Model):
    title = models.CharField(verbose_name="タイトル", ...)
    isbn = models.CharField(verbose_name="ISBN", ...)
    price = models.PositiveIntegerField(verbose_name="価格")
    author = models.ForeignKey('Author', verbose_name="著者", ...)
    genre = models.ForeignKey('Genre', verbose_name="ジャンル", ...)
    created_at = models.DateTimeField("作成日時", default=timezone.now)
    updated_at = models.DateTimeField("更新日時", auto_now=True)
    ...

やったこと

ModelForm でのフィールド指定には、fields 属性を使う方法と exclude 属性を使う方法がある。


  • fields 属性: model に指定したモデルに定義されているフィールドのうち、フォームとして使用するフィールドを明示的に指定する
  • exclude 属性: フォームとして使用しないフィールドを指定することで、指定したフィールド以外をフォーム化できる

上記の書籍登録画面では、作成日時と更新日時は不要のため、以下のように exclude 属性を使って実装した。


class BookAddForm(ModelForm)
    class Meta:
      model = Book
      exclude = ["created_at", "updated_at"]

このコードの問題点

exclude 属性を使うと以下のようなことが起こり得る。


  • Book モデルに新しいフィールドを追加した際は、そのフィールドが書籍登録画面でフォーム化してよいものかを確認する必要がある
  • フォーム化不要であれば、そのフォールドも忘れずに exclude 属性に追加する必要がある
    • このとき、exclude 属性に追加し忘れると、そのフィールドは意図せず編集可能になってしまう
    • 実装によっては画面に描画されないため、気が付かないまま編集可能フィールドとして存在してしまう
    • → セキュリティ上の問題になりうる

なぜこうなった?

  • exclude 属性を使ったほうが記述が少なく済むため、すっきりしたコードになって見通しがよくなると思った
  • 「めっちゃ便利」と思った
  • 限られた時間の中での焦りがあり、公式ドキュメントをちゃんと読んでいなかった

どうするとよさそうか?

フォーム化したいフィールドは、fields 属性を使って明示的に指定すると Good。
今回の場合は、以下のようになる。 


class BookAddForm(ModelForm)
    class Meta:
      model = Book
      fields = ["title", "isbn", "price", "author", "genre"]

Django の公式ドキュメントでも、fields 属性で指定することを強くオススメしている。

fields 属性を使って、フォームで編集すべき全てのフィールドを明示的に指定することを強くお勧めします。 そうしないと、フォームで予期せず特定のフィールドを設定できるようになり、セキュリティ上の問題が発生しやすくなります。 特に新しいフィールドがモデルに追加されたときに起こりがちです。フォームのレンダリング方法によっては、Webページで問題が目に見えないことさえあります。

The alternative approach would be to include all fields automatically, or remove only some. This fundamental approach is known to be much less secure and has led to serious exploits on major websites (e.g. GitHub). Django: 使うフィールドを選択する

上記の「セキュリティ上の問題」というのは、マス・アサインメント脆弱性のことを指しているのだと理解した。

exclude に指定すべきフィールドを指定し忘れた場合、意図せずにそのフィールドも更新可能となってしまい、 画面に描画されていなくても POST リクエストを改ざんして値を変更できてしまうのだろう (試してはいない)。

おわり

今回は「公式ドキュメントをちゃんと読もうね」という事例を振り返ってみた。
公式ドキュメントを読むってすごく当たり前のことだけど、焦っているときに当たり前のことができなくなるのはあるあるかもしれない (プログラミングに限らず)。

参考