環境
Windows 11 Home
Python 3.10.2
Django 4.0.2
venv利用あり
背景
フォームに表示する選択肢をModelから取得したい。
Modelが更新されたらフォームの選択肢も更新されてほしいが、フォームにModelから読み込むコードを書いても、もう1度manage.py runserver
されるまで更新されなかった。
views.pyを用いて動的にフォームの選択肢を更新する方法を記載する。
手順1
まず適当なMpdelを作成し、管理画面で更新できるようにする。
animalアプリケーションを作成
python manage.py startapp animal
手順2
各種pyファイルを更新する
INSTALLED_APPS = [
...
'animal',
]
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('animal/', include('animal.urls')),
]
from django.db import models
class AnimalModel(models.Model):
animal = models.CharField(verbose_name='動物', max_length=30, primary_key=True)
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']
from django.urls import path
from . import views
app_name = 'animal'
urlpatterns = [
path('', views.IndexView.as_view(), name='index')
]
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)
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)
<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
適当に4つの動物を登録した状態
手順5
AnimalFormを開く
http://127.0.0.1:8000/animal/
先ほど管理画面で登録した4種類の動物が確認できる
手順6
管理画面に戻りanimalモデルを追加する
猿とライオンを追加して計6種になった
手順7
AnimalFormの画面に戻り、CTRL+F5で再読み込みする
フォームでも追加した猿とライオンが確認できる
動的に選択フォームを更新できた
手順8
フォームを選択し、POSTボタンを押す
複数選択した動物が取得できていることを確認して終わり
解説
views.py
のget_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
させる手もあったが、この手法で必要なくなった