🍣

# DjangoのCreateViewに関して

2024/08/23に公開

DjangoのCreateViewのまとめ

すぐに忘れてしまうので、CreateViewについて備忘録としてまとめておく

公式サイトのCreateViewの説明

https://docs.djangoproject.com/ja/5.1/ref/class-based-views/generic-editing/#createview

DetailViewのコード

GitHubのCreateViewのコードです。バージョン5.1です

https://github.com/django/django/blob/5.1/django/views/generic/edit.py#L185

DetialView

オブジェクトの作成、(もしある場合は) バリデーションエラーとフォームの再描画、そしてオブジェクトの保存のフォームを表示するビューです。CreateViewを継承したViewにおいて、1件のオブジェクトを作成するために利用します

簡単に言うと、指定したモデルクラスを1件作成します

簡単なCreateView

基本形です

  • 「model」はモデル名として「Monster」を指定します。
  • 「fields」で画面に表示するフォームパーツを指定します。
  • 「template_name」ではテンプレートファイルを指定します。デフォルトでは、「モデル名_form.html」になります。アプリ名とモデル名でディレクトリ分割したいのでディレクトリで区切ります
  • テンプレートで利用するフォームの変数は「form」です
  • 「success_url」では、新規登録が正常に完了後にリダイレクトされるURLを指定します。URLを文字列で指定します。DjangoのURL名で指定する場合、「reverse_lazy」で囲みます
views.py
from django.views.generic import CreateView
from django.urls import reverse_lazy
from .models import Monster

class MonsterCreateView(CreateView):
  model = Monster
  fields = ["name", "hp", "mp", "type", "birthday"]
  template_name = "study/monster/create.html"
  success_url = reverse_lazy("study:monster_list")

テンプレートファイル

対応するテンプレートファイルです。

  • base.htmlを作成して、継承します
  • django_bootstarap5を利用します
  • Bootstrap5でスタイリングします
  • フォームの変数名は「object」です
  • 一覧のURL:study:monster_list
  • 新規作成のURL:study:monster_create
  • 更新のURL:study:monster_update
study/monster/create.html
{% extends "base.html" %}

{% load django_bootstrap5 %}

{% block title%}{{ title }}{% endblock %}

{% block content %}
<div class="container mt-4">
    <h2 class="mb-4 p-2 bg-primary text-white">Monster Create</h2>
    
    <div class="mb-3">
        <a href="{% url 'study:monster_list' %}" class="btn btn-primary">一覧に戻る</a>
    </div>
    
    <form action="{% url 'study:monster_create' %}" method="post" novalidate>
        {% csrf_token %}
        {% bootstrap_form form %}
        <button type="submit" class="btn btn-primary">送信</button>
    </form>
</div>
{% endblock %}

ModelFormクラスの作成

Modelクラスと連動したフォームクラスを作成します。ModelFormを継承したMonsterFormクラスを作成します。model, fields, widgetsなどの設定を行います

forms.py
class MonsterForm( forms.ModelForm):
  class Meta:
    model = Monster
    fields = ['name', 'hp', 'mp', 'type', 'birthday']
    widgets = {
        'name' : forms.TextInput(attrs={'autofocus' : True}),
        'birthday' : forms.TextInput(attrs={'type' : 'date'}),
    }

フォームクラスを利用する

model, fieldsの設定を取り除きます。逆に、form_classを指定します。MonsterFormを指定します。これにより、フォームの設定の自由度が増します。各フィールドのwidgetなどを細かく設定可能です。こちらを利用する方が自由度が高い

views.py
class MonsterCreateView(CreateView):
  form_class = MonsterForm
  template_name = "study/monster/create.html"
  success_url = reverse_lazy("study:monster_list")

コンテキストにデータを渡す

get_context_dateメソッドをオーバーライドすることで、コンテキストに追加データを渡すことができます。つまり、テンプレートで利用するデータを指定します

views.py
class MonsterCreateView(CreateView):
  form_class = MonsterForm
  template_name = "study/monster/create.html"
  success_url = reverse_lazy("study:monster_list")

  def get_context_data(self, **kwargs):
    context = super().get_context_data(**kwargs)
    context["title"] = self.__class__.__name__
    return context  

get_form

フォームを取得します。実際の利用としては、フォーム画面を表示する際に、フォームの情報を操作する際に利用します。戻り値はformです

以下の例では、ModelFormの「name」フィールドの初期値として「Slime」を代入します。これにより、テキストボックスに「Slime」が代入されています。

views.py
class MonsterCreateView(CreateView):
  form_class = MonsterForm
  template_name = "study/monster/create.html"
  success_url = reverse_lazy("study:monster_list")

  def get_form(self):
    form = super().get_form()
    form.fields["name"].initial = "Slime"
    return form

フォームクラスでも初期値の設定は可能ですが、viewで行うことで、動的な値を設定可能になります。例えば、ログインユーザに基づいて、値を設定するなどが可能になります

多対1で結合している場合、多側の新規作成時に1側をセレクトボックスで選択します。セレクトボックスの選択肢として、1側のモデルがすべて選択肢に表示されます。選択肢を絞り込むためにも利用可能です。以下の方法で、1側のモデルの選択肢を変更します

views.py
  def get_form(self):
    form = super().get_form()
    form.fields["フィールド名"].queryset = モデル名.objects.filter(xxx=yyy)
    return form

form_valid

フォームのサブミット後、バリデーションがエラーが発生しない場合、「form_valid」が呼び出されます。「form_valid」をオーバーライドすることで、バリデーションエラーが未発生後の処理をカスタマイズ可能です。第2引数「form」はユーザが入力した情報を含んだフォームクラスです。戻り値は遷移先のURLを指定します

以下の例では、フォームをそのまま保存しています。単純に新規作成するだけなので、ほぼ意味のない処理です。

views.py
from django.shortcuts import redirect

class MonsterCreateView(CreateView):
  form_class = MonsterForm
  template_name = "study/monster/create.html"
  success_url = reverse_lazy("study:monster_list")

  def form_valid(self, form):
    form.save()
    return redirect(self.success_url)

「form_valid」に意味があるのは、以下のようなケースです。フォームクラスのフィールドでモデルに登録するために必要なフィールドをすべて指定していない場合、プログラムで必須フィールドに値を設定する必要があります。その場合、「form_valid」でプログラムで値を代入して、保存します

views.py
from datetime import datetime

class MonsterCreateView(CreateView):
  form_class = MonsterForm
  template_name = "study/monster/create.html"
  success_url = reverse_lazy("study:monster_list")

  def form_valid(self, form):
    # フォームからモデルを取り出します。この時点では保存されない
    model = form.save(commit=False)
    # モデルの各フィールドに値を代入
    model.hp = 100
    model.mp = 100
    model.type = Monster.TYPE_CHOICES[0][0]
    model.birthday = datetime.now()
    # モデルを保存。新規作成します
    model.save()
    # リダイレクトします
    return redirect(self.success_url)

form_invalid

フォームのサブミット後、バリデーションエラーが発生した場合、呼び出されるメソッドです

Discussion