Набор форм Django действителен, но возвращает пустой список dict
Я пытаюсь создать форму, представляющую собой таблицу, в которой каждая строка содержит имя и флажок, позволяющий пользователю выбирать игроков для игры. Погуглив, я понял, что мне нужно сгенерировать набор форм, чтобы каждая строка в таблице была отдельной формой.
Наконец-то таблица отображается корректно и публикуется без ошибок, но я обнаружил, что, хотя набор форм действителен, очищенные данные содержат список пустых диктов. Я нашел несколько похожих постов на StackOverflow, в которых говорилось о создании собственного класса Formset и установке empty_permitted=True, но это не решило мою проблему.
Я не могу решить эту проблему, поэтому любая помощь будет оценена по достоинству.
Сниппеты кода
# forms.py
class InvitePlayerForm(forms.Form):
"""
Form to invite players to the new game
"""
invited = forms.CheckboxInput()
class InvitePlayerFormset(BaseFormSet):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for form in self.forms:
form.empty_permitted = False
# views.py:
def create_game_players(request):
"""
View to invite players to a game
"""
context = dict()
# if this is a POST request we need to process the form data
if request.method == "POST":
num_players = request.session["invites"]["num_players"]
print(f"num_players: {num_players}")
invite_formset = formset_factory(InvitePlayerForm, formset=InvitePlayerFormset, extra=num_players)
invite_forms = invite_formset(data=request.POST)
print(f"Formset valid: {invite_forms.is_valid()}")
print(f"request.POST: {request.POST}")
print(f"changed_data: {invite_forms.has_changed}")
data = invite_forms.cleaned_data
print(f"Cleaned data: {data}")
return HttpResponseRedirect(reverse("game_list_view"))
# if a GET (or any other method) we'll create a blank form
else:
player_list = list(CustomUser.objects.all())
num_players = len(player_list)
invite_formset = formset_factory(InvitePlayerForm, formset=InvitePlayerFormset, extra=num_players)
# context["num_players"] = num_players
context["first_names"] = [u.first_name for u in player_list]
context["last_names"] = [u.last_name for u in player_list]
context["invite_formset"] = invite_formset()
request.session["invites"] = {"num_players": num_players}
return render(request, "create_game_players.html", context)
# create_game_players.html template
<h4>Invite Players</h4>
<div class="form-group">
<form method="post">
{% csrf_token %}
{{ invite_formset.management_form.as_p }}
<table class="table table-hover">
<thead>
<tr>
<th>Name</th>
<th>Invited</th>
</tr>
</thead>
<tbody>
{% for invite_form in invite_formset.forms %}
<tr>
<td>{{first_names|get:forloop.counter0}} {{last_names|get:forloop.counter0}}</td>
<td>
<input type="checkbox" class="form-check-input" name="invited_{{forloop.counter0}}" id="{{forloop.counter0}}"/>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<button type="submit" class="btn btn-info btn-lg btn-block">Next</button>
</form>
</div>
Вывод консоли
[05/Apr/2024 14:32:31] "POST /create_game/ HTTP/1.1" 302 0
[05/Apr/2024 14:32:31] "GET /create_game/players HTTP/1.1" 200 8961
num_players: 21
Formset valid: True
request.POST: <QueryDict: {'csrfmiddlewaretoken': ['HzZu3tHpBIHDVSwPa4UJZTjWCNC52s7Njz2NRhTvJPXDzNKsSonFNRxyUNK1NtZu'], 'form-TOTAL_FORMS': ['21'], 'form-INITIAL_FORMS': ['0'], 'form-MIN_NUM_FORMS': ['0'], 'form-MAX_NUM_FORMS': ['1000'], 'invited_0': ['on'], 'invited_1': ['on'], 'invited_2': ['on']}>
changed_data: <bound method BaseFormSet.has_changed of <InvitePlayerFormFormSet: bound=True valid=True total_forms=21>>
Cleaned data: [{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}]
[05/Apr/2024 14:32:39] "POST /create_game/players HTTP/1.1" 302 0
Я думаю, что вместо обращения к invite_forms.cleaned_data
следует пройтись циклом по каждой форме в наборе форм и проверить значение поля 'invited'
.
Попробуйте этот вид:
def create_game_players(request):
"""
View to invite players to a game
"""
context = dict()
# if this is a POST request we need to process the form data
if request.method == "POST":
num_players = request.session["invites"]["num_players"]
print(f"num_players: {num_players}")
invite_formset = formset_factory(InvitePlayerForm, formset=InvitePlayerFormset, extra=num_players)
invite_forms = invite_formset(data=request.POST)
print(f"Formset valid: {invite_forms.is_valid()}")
print(f"request.POST: {request.POST}")
print(f"changed_data: {invite_forms.has_changed}")
if invite_forms.is_valid():
selected_players = []
for form in invite_forms:
if form.cleaned_data.get('invited'):
selected_players.append(form.cleaned_data.get('invited'))
print(f"Selected players: {selected_players}")
return HttpResponseRedirect(reverse("game_list_view"))
# if a GET (or any other method) we'll create a blank form
else:
player_list = list(CustomUser.objects.all())
num_players = len(player_list)
invite_formset = formset_factory(InvitePlayerForm, formset=InvitePlayerFormset, extra=num_players)
# context["num_players"] = num_players
context["first_names"] = [u.first_name for u in player_list]
context["last_names"] = [u.last_name for u in player_list]
context["invite_formset"] = invite_formset()
request.session["invites"] = {"num_players": num_players}
return render(request, "create_game_players.html", context)
Также убедитесь, что поле 'invited'
в вашей форме правильно определено и сопоставлено с флажками в вашем шаблоне.
Редактирование:
Попробуйте использовать BooleanField
в формах, так:
class InvitePlayerForm(forms.Form):
"""
Form to invite players to the new game
"""
invited = forms.BooleanField(label='Invited', required=False)
Затем в своем шаблоне используйте правильный атрибут name для ввода checkbox:
<input type="checkbox" class="form-check-input" name="invited_{{ forloop.counter0 }}" id="{{ forloop.counter0 }}"/>