【Python】【Django】ChoiceFieldの使い方と選択肢の追加方法

# 概要
DjangoでChoiceFieldを触っていて、時間のかかった箇所を忘備録として残しておきます。  
サンプルコードは[こちら](https://github.com/yui728/choicefield_sample)

# 目次
 - [ChoiceFieldについて](#ChoiceFieldについて)
   - [サンプル](#サンプル)
 - [時間のかかった箇所](#時間のかかった箇所)
    - [選択肢を後から追加する方法](#選択肢を後から追加する方法)
    - [Fieldの`choices`プロパティ](#fieldのchoicesプロパティ)
       - [選択肢を後から追加するChoiceFieldのコード](#選択肢を後から追加するchoicefieldのコード)

# ChoiceFieldについて
 - 複数の選択肢から一つを選ぶフィールド
 - デフォルトの描画は`django.forms.widgets.Select`
    - テンプレートに描画すると`<select name="フィールド名"><option value="choice.key">choice.value</option>...</select>`となる
 - 参考：[Form Fields：ChoiceField](https://docs.djangoproject.com/ja/2.2/ref/forms/fields/#choicefield)

## サンプル
~~~python:forms.py
from django import forms


class SampleChoiceForm(forms.Form):
    choice1 = forms.fields.ChoiceField(
        choices = (
            ('ja', '日本'),
            ('us', 'アメリカ'),
            ('uk', 'イギリス'),
            ('ch', '中国'),
            ('kr', '韓国')
        ),
        required=True,
        widget=forms.widgets.Select
    )
~~~

~~~python:views.py
from django.shortcuts import render
from django.views import View
from . import forms

class SampleChoiceView(View):
    def get(self, request):
        form = forms.SampleChoiceForm()
        context = {
            'form': form
        }

        return render(request, 'choice_sample.html', context)

sample_choice_view = SampleChoiceView.as_view()
~~~

~~~html:choice_sample.html
<!DOCTYPE html>
<html lang="ja">
    <head>
        <title>ChoiceFieldサンプル</title>
    </head>
    <body>
        <form method="POST" aciton="">
            {% for field in form %}
            {{ field }}
            {% endfor %}
            {% csrf_token %}
        </form>
    </body>
</html>
~~~

~~~html:出力結果
<!doctype html>
<html lang="ja">
<head>
<title>ChoiceFieldサンプル</title>
</head>
<body>
  <form method="POST" action="">
    <select id="id_choice1" name="choice1">
      <option value="ja">日本</option>
      <option value="us">アメリカ</option>
      <option value="uk">イギリス</option>
      <option value="ch">中国</option>
      <option value="kr">韓国</option>
    </select>
    <input type="hidden" name="csrfmiddlewaretoken" value="xxxxxxxx" />
  </form>
</body>
</html>
~~~
### 表示
![表示結果1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27600/ae66dc05-de0f-b529-9e62-12280303b906.png)


# 時間のかかった箇所
 - 選択肢を後から追加する方法

## 選択肢を後から追加する方法
公式チュートリアルやDjango Girls チュートリアルでは、選択肢のあるフィールドはモデルから選択肢を取得したり、固定の選択肢をChoiceFieldの`choice`パラメータで初期値として与えるという形式で書かれています。  
ですが、この選択肢を動的に作成したいとなった場合はどうしたら良いでしょうか？

## Fieldの`choices`プロパティ
Djangoのドキュメントに[モデルフィールドリファレンス](https://docs.djangoproject.com/ja/2.2/ref/models/fields/)というページがあり、
ここには、モデルフィールドを含めた、フォームで使用するフィールドについて書かれています。
探してみると、[`Field.choices`](https://docs.djangoproject.com/ja/2.2/ref/models/fields/#choices)という項があります。 
念の為、[ChoiceFieldのソースコード](https://docs.djangoproject.com/ja/2.2/_modules/django/forms/fields/#ChoiceField)も見てみましょう。  


~~~python
class ChoiceField(Field):
（中略）
    def _get_choices(self):
        return self._choices

    def _set_choices(self, value):
    # Setting choices also sets the choices on the widget.
    # choices can be any iterable, but we call list() on it because
    # it will be consumed more than once.
        if callable(value):
             value = CallableChoiceIterator(value)
        else:
              value = list(value)

        self._choices = self.widget.choices = value

   choices = property(_get_choices, _set_choices)
（後略）
~~~

確かに`choices`というプロパティが存在しています。    
では、使ってみましょう。  

### 選択肢を後から追加するChoiceFieldのコード

~~~python:forms.py
from django import forms
（中略）
class SampleChoiceAddForm(forms.Form):
    choice1 = forms.fields.ChoiceField(
        required=True,
        widget=forms.widgets.Select
    )
~~~

~~~python:views.py
from django.shortcuts import render
from django.views import View
from . import forms
（中略）
class SampleChoiceAddView(View):
    def get(self, request):
        form = forms.SampleChoiceAddForm()
        form.fields['choice1'].choices = [
            ('1', '1番目'),
            ('2', '2番目'),
            ('3', '3番目'),
            ('4', '4番目'),
            ('5', '5番目'),
        ]
        context = {
            'form': form
        }

        return render(request, 'choice_sample.html', context)
（中略）
sample_choice_add_view = SampleChoiceAddView.as_view()
~~~

~~~html:出力結果
<!DOCTYPE html>
<html lang="ja">
<head>
<title>ChoiceFieldサンプル</title>
</head>
<body>
  <form method="POST" aciton="">
    <select name="choice1" id="id_choice1">
       <option value="1">1番目</option>
       <option value="2">2番目</option>
       <option value="3">3番目</option>
       <option value="4">4番目</option>
       <option value="5">5番目</option>
    </select>
    <input type="hidden" name="csrfmiddlewaretoken" value="xxxxxxxx">
  </form>
</body>
</html>
~~~

### 表示
![表示結果2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27600/e48cf43c-f597-c6a7-78b9-88779a353c65.png)


選択肢の後から追加、行けました！