webアプリ my-favorite-list フォーム機能

# フォーム機能

したこと：リレーション

ユーザー入力を管理する

- フォームを定義
- データベースにデータを保存
- モデルと紐づけ
- データのバリテーション（データの型が正しいかどうか）

# 流れ

 forms.py → scripts.py → views.py → urls.py → html

# 役割

 フォームの定義 -> 選択肢 -> 見た目 -> urlの設定 -> html(ユーザーの見た目)

## forms.py

- リレーションのフォームを定義
- class metaでフォームで使うmodel, field, widgetを指定。
- defでフォームの初期化
- 編集の際はリレーションの情報を取得

```python
#bookフォーム
class BookForm(forms.ModelForm):
	# 分類のチェックボックス
	types = forms.ModelMultipleChoiceField( #formsのデータベースモデルから複数選択できる
	
	queryset=BookType.objects.all(), #選択肢のリスト = BookTypeからすべてのデータ取得
	widget=forms.CheckboxSelectMultiple(attrs={'class': 'form-check-input'}), #見た目 = attrs(htmlの属性)はチェックボックス
	label='分類',  # タイトル「分類」に変更
	required = False, #空欄のままでもOK
	)
	# ジャンルのチェックボックス
	genres = forms.ModelMultipleChoiceField(
	queryset=BookGenre.objects.all(),
	widget=forms.CheckboxSelectMultiple(attrs={'class': 'form-check-input'}),
	label='ジャンル',  # 修正: 本来のタイトルへ
	required=False,
	)
	# 状態のチェックボックス
	statuses = forms.ModelMultipleChoiceField(
	queryset=BookStatus.objects.all(),
	widget=forms.CheckboxSelectMultiple(attrs={'class': 'form-check-input'}),
	label='状態',  # 修正: 本来のタイトルへ
	required = False,
	)
	# 評価のチェックボックス
	values = forms.ModelMultipleChoiceField(
	queryset=Value.objects.all(),
	widget=forms.CheckboxSelectMultiple(attrs={'class': 'form-check-input'}),
	label='評価',  # 修正: 本来のタイトルへ
	required=False,
	)

	class Meta:
	    model = Book
	    fields = ['title', 'author', 'designer', 'types', 'genres', 'series', 'statuses', 'values']
	    widgets = {
	        'title': forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'タイトルを入力してください'}),
	        'author': forms.TextInput(attrs={'class': 'form-control', 'placeholder': '著者名を入力してください'}),
	        'designer': forms.TextInput(attrs={'class': 'form-control', 'placeholder': '絵師を入力してください'}),
	        'series': forms.NumberInput(attrs={'class': 'form-control', 'placeholder': '巻数を入力してください'}),
	    }

		def __init__(self, *args, **kwargs):
		    request = kwargs.pop('request', None)
		    super().__init__(*args, **kwargs)
		
		    if request and request.path == '/book/search/':
		        self.fields.pop('series')
		
		    if self.instance and self.instance.pk:  # 編集モードの場合
		        # ManyToManyFieldの初期値を設定
		        self.fields['types'].initial = self.instance.type.all()
		        self.fields['genres'].initial = self.instance.genre.all()
		        self.fields['statuses'].initial = self.instance.status.all()
		        self.fields['values'].initial = self.instance.value.all()

```

## scripts.py

- for in []で選択肢を作る
- 保存：model名.objects.get_or_create(name=name)

```python
#Valueのデータ登録

for name in ['SS', 'S', 'A', 'B', 'C', 'D']:
	Value.objects.get_or_create(name=name)

#BookTypeのデータ登録

for name in ['小説', 'ライトノベル', '漫画', 'ビジネス本', 'お金']:
	BookType.objects.get_or_create(name=name)

#BookGenreのデータ登録

for name in ['推理・ミステリー', '青春', '恋愛', 'SF(サイエンスフィクション)', 'ファンタジー',
'ホラー', '経済', '政治', '歴史・時代', 'ミリタリー', '児童', '官能']:
	BookGenre.objects.get_or_create(name=name)

#BookStatusのデータ登録

for name in ['未読', '読書中', '読了', '購入済み', '気になる']:
	BookStatus.objects.get_or_create(name=name)
```

## views.py

- 選択肢１：int:pk等の場合、pkをコードで受け取っている場合勝手にurls.pyで解析してくれる
- 選択肢２：新規作成
- リクエストがある場合：保存 -> book_list(一覧ページに飛ぶ)
- リクエストがない場合：fieldの情報を追加
- render(book_formのページに情報を渡す)

```python
def book_form(request, pk=None):
	if pk:  # pkが指定されている場合、編集モード
		book = get_object_or_404(Book, pk=pk)  #urls.pyでpkを解析してくれる
		form = BookForm(instance=book)
	else:  # pkがない場合、新規作成モード
		book = None
		form = BookForm()
		
	if request.method == 'POST':  # POSTリクエストの場合、フォームを保存
    form = BookForm(request.POST, instance=book) #フォームにPOSTデータを関連づけ（結びつけ）
    if form.is_valid():
        print(form.cleaned_data)
        book = form.save(commit=False) #インスタンス作成
        book.save() #データベースに保存
        form.save_m2m() #manytomanyfiledの関連データを保存 (今回はできなかった)

        # ManyToManyFieldに手動で関連付けを追加
        book.type.set(form.cleaned_data.get('types', []))
        book.genre.set(form.cleaned_data.get('genres', []))
        book.status.set(form.cleaned_data.get('statuses', []))
        book.value.set(form.cleaned_data.get('values', []))

        return HttpResponseRedirect(reverse('book_list'))  # リダイレクト

# 各フィールドの情報を追加
	field_data = []
	for field in form:
	    field_data.append({
	        'field': field,
	        'is_checkbox': isinstance(field.field.widget, forms.CheckboxSelectMultiple)  # チェックボックス判定(ラジオボタンやドロップダウンリストにも必要)
	    })

# テンプレートに渡す
	return render(request, 'myapp/book_form.html', {'field_data': field_data})

```

## urls.py

- 新しいページ
- 編集ページ

```python
path('book/form/', views.book_form, name='book_form'),
path('book/form/int:pk/', views.book_form, name='book_edit'),
```