LoginSignup
4
1

More than 1 year has passed since last update.

Django 複数選択フォームを動的に更新する(get_form_kwargsとMultipleChoiceField)

Last updated at Posted at 2022-10-16

環境

Windows 11 Home
Python 3.10.2
Django 4.0.2
venv利用あり

背景

フォームに表示する選択肢をModelから取得したい。
Modelが更新されたらフォームの選択肢も更新されてほしいが、フォームにModelから読み込むコードを書いても、もう1度manage.py runserverされるまで更新されなかった。
views.pyを用いて動的にフォームの選択肢を更新する方法を記載する。

ゴールイメージはこんな形
image.png

手順1

まず適当なMpdelを作成し、管理画面で更新できるようにする。

animalアプリケーションを作成
python manage.py startapp animal

手順2

各種pyファイルを更新する

mysite\settings.py
INSTALLED_APPS = [
    ...
    'animal',
]
mysite\urls.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('animal/', include('animal.urls')),
]
animal\models.py
from django.db import models

class AnimalModel(models.Model):
    animal = models.CharField(verbose_name='動物', max_length=30, primary_key=True)
animal\admin.py
from django.contrib import admin

from .models import AnimalModel

class AnimalModelClass(AnimalModel):
    class Meta:
        verbose_name = ('動物')
        verbose_name_plural = ('動物')

@admin.register(AnimalModelClass)
class AnimalModelAdmin(admin.ModelAdmin):
    list_display = ['animal']
    search_fields = ['animal']
animal\urls.py
from django.urls import path
from . import views

app_name = 'animal'

urlpatterns = [
    path('', views.IndexView.as_view(), name='index')
]
animal\views.py
from django.shortcuts import render
from django.views.generic.edit import FormView
from .models import AnimalModel
from .forms import AnimalForm

class IndexView(FormView):
    template_name = 'animal/index.html'
    form_class = AnimalForm

    def get_form_kwargs(self, *args, **kwargs):
        kwgs = super().get_form_kwargs(*args, **kwargs)

        products = AnimalModel.objects.values_list('animal', flat=True)
        choice_ls = [(product, product) for product in products]
        kwgs['categories'] = choice_ls
        return kwgs

    def form_valid(self, form):
        print("hello")
        message = f"選択された動物は{form.cleaned_data['animal']}で、備考は{form.cleaned_data['comment']}です。"
        context = {'form':form, 'msg':message}
        return render(self.request, self.template_name, context)
animal\forms.py
from django import forms
from .models import AnimalModel

class AnimalForm(forms.Form):
    animal = forms.MultipleChoiceField()
    comment = forms.CharField(
        label='備考',
        max_length=200,
        required=False,
        widget=forms.TextInput()
    )

    class Meta:
        model = AnimalModel
        field = "__all__"

    def __init__(self, categories=None, *args, **kwargs):
        self.base_fields['animal'].choices = categories
        super().__init__(*args, **kwargs)
animal\templates\animal\index.html
<body>
    <form method='POST'>
        {% csrf_token %}
        <table>
            {{ form.as_table}}
        </table>
        <button type='submit'>POST</button>
    </form>
    {{ msg }}
</body>

手順3

migrateしてRunserverする
python manage.py makemigrations
python manage.py migrate
python manage.py runserver

手順4

管理画面にログインしanimalモデルを更新する
http://127.0.0.1:8000/admin
image.png
適当に4つの動物を登録した状態

手順5

AnimalFormを開く
http://127.0.0.1:8000/animal/
image.png
先ほど管理画面で登録した4種類の動物が確認できる

手順6

管理画面に戻りanimalモデルを追加する
image.png
猿とライオンを追加して計6種になった

手順7

AnimalFormの画面に戻り、CTRL+F5で再読み込みする
image.png
フォームでも追加した猿とライオンが確認できる
動的に選択フォームを更新できた

手順8

フォームを選択し、POSTボタンを押す
image.png
複数選択した動物が取得できていることを確認して終わり

解説

views.pyget_form_kwargsを使い、クラスベースビューからフォームクラスへ値を渡している
views.pyは画面が読み込まれるたびに呼ばれるため、その時の最新のAnimalModelを取得しに行く
products = AnimalModel.objects.values_list('animal', flat=True)の部分が該当
フォームクラスでは__init__を使い、ビュークラスで設定した辞書型のキーを引数として受け取っている

このあたりの処理をviews.pyではなくforms.pyに記載しても冒頭に記載したようにもう1度manage.py runserverされるまで更新されなかった
シェルスクリプト等で定期的にmanage.py runserverさせる手もあったが、この手法で必要なくなった

4
1
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
4
1