django formset not validating because ID is required
Asked Answered
B

3

17

MY view receives a model formset from the template, but it doesn't pass validation, claiming that ID is required. Al my use of forms until now has never brought up this problem, and I've never had to pass ID's around.

Here is a simplified version of my view:

def BudgetView(request):

    import pdb
    pdb.set_trace()

    if request.user.is_authenticated:
        U=request.user

        #initalize formset factories
        ItemFormSet = modelformset_factory(Item, fields=(blabla), extra=0)
        CatFormset=modelformset_factory(BudgetCatagory, fields=(blabla), extra=0)

        #initalize Constants
        InitiateConstants(CatagoryItemsList)

        if request.method=='POST':
            FormsetItem=ItemFormSet(request.POST,initial=Item.objects.filter(budgetcatagory__user_id=U.id).values())
            FormsetCat=CatFormset(request.POST)
            if FormsetItem.is_valid():
-bla
-bla
-bla

            return redirect('/HighLevelInput')
        else:
            #populate
            I=Item.objects.filter(budgetcatagory__user_id=U.id)
            C=BudgetCatagory.objects.filter(user_id=U.id)

            #initiate initial catagories and items for new user
            if (not I.exists()) or (not C.exists()):
                Item.objects.filter(budgetcatagory__user_id=U.id).delete()
                BudgetCatagory.objects.filter(user_id=U.id).delete()
                InitiateNewUser(U)
                I=Item.objects.filter(budgetcatagory__user_id=U.id)
                C=BudgetCatagory.objects.filter(user_id=U.id)
            FormsetItem=ItemFormSet(queryset=I)
            FormsetCat=CatFormset(queryset=C)

        return render(request,'getdata/budgetmachine.html', {'FormsetItem':FormsetItem, 'FormsetCat':FormsetCat })
    else:
        return redirect('/login')

is_valid returns False for the reason I've mentioned above. any ideas?

As requested, here are the errors returned from the is_valid check:

(Pdb) FormsetItem.errors
[{'id': ['This field is required.']}, {'id': ['This field is required.']}, {'id': ['This field is required.']}, {'id': ['This field is required.']}, {'name': ['This field is required.'], 'detail': ['This field is required.'], 'layout': ['This field is required.'], 'unit': ['This field is required.'], 'unit_description': ['This field is required.'], 'parent': ['This field is required.'], 'enName': ['This field is required.'], 'id': ['This field is required.']}, {'id': ['This field is required.']}, {'id': ['This field is required.']}, {'id': ['This field is required.']}, {'id': ['This field is required.']}, {'id': ['This field is required.']}, {'id': ['This field is required.']}, {'id': ['This field is required.']}, {'id': ['This field is required.']}, {'id': ['This field is required.']}, {'id': ['This field is required.']}, {'name': ['Ensure this value has at most 30 characters (it has 32).'], 'parent': ['Ensure this value has at most 30 characters (it has 32).'], 'id': ['This field is required.']}, {'id': ['This field is required.']}, {'id': ['This field is required.']}, {'id': ['This field is required.']}, {'id': ['This field is required.']}, {'id': ['This field is required.']}, {'id': ['This field is required.']}, {'id': ['This field is required.']}, {'id': ['This field is required.']}, {'id': ['This field is required.']}, {'id': ['This field is required.']}]

And here is my template:

{% block body %}

<div class="container" style="width:80%">

  <form method="post">
    {% csrf_token %}
    {{ FormsetItem.management_form }}
    {{ FormsetCat.management_form }}

    <table>
      <tr>
        <th>פריט</th>
        <th>מחיר מתוקצב</th>
        <th>מיקום מחיר</th>
        <th>רמת פירוט</th>
        <th>רמת פירוט</th>
      </tr>

      <!--unpacks the item dictionary into formsets -->
      {% for CatForm in FormsetCat %}
        <tbody onmouseenter="ToggleDisable('{{ CatForm.enName.value}}')" onmouseleave="ToggleDisable('{{ CatForm.enName.value}}')">
          {% for ItemForm in FormsetItem %}
            {% if ItemForm.parent.value == CatForm.name.value %}
                {% if ItemForm.layout.value == 'normal' %}
                  {% include 'getdata/normalBudgetLayout.html' with form=ItemForm itemCount=forloop.counter0 catagoryCount=forloop.parentloop.counter0 %}
                {% elif ItemForm.layout.value == 'choice' %}
                  {% include 'getdata/choiceBudgetLayout.html' with form=ItemForm itemCount=forloop.counter0 catagoryCount=forloop.parentloop.counter0 %}
                {% endif %}
            {% endif %}
          {% endfor %}
        <tr class="txt_center Row_{{ CatForm.enName.value}}" style="line-height:4em; background:Silver;">
          <td>
            <a onclick="ToggleDisable('{{ CatForm.enName.value}}')">
              {{ CatForm.name.value }}
              <span style="float:left;">
                <i class="fa fa-caret-down {{ CatForm.enName.value}}" style="font-size:30px; padding:7px;"></i>
                <i class="fa fa-caret-up {{ CatForm.enName.value}}" style="font-size:30px; padding:7px;" hidden></i>
              </span>
            <!--/a-->
          </td>
          <td>{{ CatForm.catagory_cost }}</td>
          <td>TBD</td>
          <td>{{ CatForm.detail.value }}</td>
          <td></td>
        </tr>
        </tbody>
      {% endfor %}
    </table>
    <br />
    <br />
    <br />
    <button type="submit" >Submit</button>    <br />
    <br />

  </form>
</div>




{% endblock %}

Thx

Bole answered 28/2, 2018 at 9:33 Comment(4)
Please show the exact errors. Note that passing initial in the POST block does nothing at all. And why aren't you passing queryset there like you do in the else block?Metencephalon
New to this, so I'm learning new stuff everyday... I wanted to use a queryset but I read that initial only takes a lists of dictionaries, while queryset takes, well, a queryset which is objects. Also, I read that using initial initiates a comparison between the data in request, and only updates what has been changed or added.... will try your points, thanks. Still left with the problem though... It doesn't give me an error, just is_valid returns false and the error field shows that id is required.Bole
You still need to show what that error field contains. And you should show your template as well. But I'm very very confused about your point about initial/queryset. You should be passing a queryset always and only passing initial (if you need it at all) in the non-POST case.Metencephalon
Do you mean something like this: FormsetItem=ItemFormSet(request.POST, queryset=Item.objects.filter(budgetcatagory__user_id=U.id)) ??Bole
A
31

ModelFormsets require form.id. It is rendered as a hidden field. You will need to implement it with both formsets.

{% for form in formset %}
    {% for hidden in form.hidden_fields %}
        {{ hidden }}
    {% endfor %}
    <!-- form.visible fields go here -->
{% endfor %}
Absa answered 26/3, 2019 at 16:6 Comment(2)
In addition, it seems that using management_form occurs this problem. Because management_form means you will manage all of them, so hidden fields also need to managed.Distillery
also see @j-a-n-u-s answer belowPeppery
N
9

As an addendum to @unixo 's answer, simply putting:

{{ form.id }}

without any surrounding HTML tags, will be converted to the following when the template is rendered (values for name, value and id will be generated by your formset_factory):

<input type="hidden" name="form-1-id" value="2" id="id_form-1-id">

Just make sure it's indented into the for form in formset loop.

Meaning you don't need to add class="hidden" unless you have some particular handling of hidden fields you want.

Nagle answered 19/7, 2019 at 16:42 Comment(1)
This solution worked where the standard one (Cyclying through hidden fields) didn't. Awesome! It solved a really sticky problem for me.Pajamas
J
3

The error message is very clear in this case: you've to render the "id" field otherwise the POST won't contain the primary key value of each record.

I'd suggest using crispy forms and let it renders the entire formset or manually render the field in the template. In the first case, you'd have something like this:

{% load crispy_forms_tags %}
<form action="post" ...>
    {% crispy formset %}
</form>

Otherwise:

<form action="post" ...> 
    <table>
        <tbody>
           {% for form in formset %}
           <tr>
              <td>{{ form.field1 }}</td>
              <td>{{ form.field2 }}</td>
              <td class="hidden">{{ form.id }}</td>
           </tr>
        </tbody>
    </table>             
</form>
Jacobine answered 6/4, 2018 at 17:39 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.