Набор форм 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 }}"/>
Вернуться на верх