Django - 更新内联表单集不更新

Django - update inline formset not updating

我正在尝试创建一个更新视图,其中包含一些内联表单集,但出于某种原因,我看到 'id: This field is required.' 和 none 设置了内联表单集值单击更新按钮。这些字段本身在模板中实际上有值,所以我不确定为什么在尝试保存时内联表单集会为空。

Models.py

class ProjectUpdateForm(forms.ModelForm):
class Meta:
    model = Project
    fields = ('name', 'details')

Views.py

class ProjectUpdateView(LoginRequiredMixin, UpdateView):
model = Project
template_name = 'home/project/project_update.html'
context_object_name = "project"
form_class = ProjectUpdateForm

def get_context_data(self, **kwargs):
    ReviewChildFormset = inlineformset_factory(
        Project, AdoptedBudgetReview, fields=('project', 'review'), can_delete=False, extra=0
    )

    itemNames = [{'item': item} for item in ['Concept & Feasibility', 'Planning & Design', 'Procurement', 'Delivery & Construction', 'Finalisation']]
    EstimatedBudgetChildFormset = inlineformset_factory(
        Project, EstimatedBudget, fields=('project', 'item', 'cost', 'time'), can_delete=False, formset=EstimatedInlineFormset, extra=0, widgets={'item': forms.Select(attrs={'disabled': True})},
    )

    FutureExpenditureFormset = inlineformset_factory(
        Project, FutureExpenditure, fields=('project', 'byear', 'internalFunding', 'externalFundingSource', 'externalFunding'), can_delete=False, extra=0,
    )

    PreviousExpenditureFormset = inlineformset_factory(
        Project, PreviousExpenditure, fields=('project', 'byear', 'internalFunding', 'externalFunding'), can_delete=False, extra=0,
    )

    initial = [{'priority': priority} for priority in Priority.objects.all()]
    PriorityChildFormset = inlineformset_factory(
        Project, ProjectPriority, fields=('project', 'priority', 'score'), can_delete=False, extra=0, widgets={'priority': forms.Select(attrs={'disabled': True})},
    )

    context = super().get_context_data(**kwargs)
    if self.request.POST:
        context['adoptedreviews'] = ReviewChildFormset(self.request.POST, instance=self.object)
        context['estimatedbudget'] = EstimatedBudgetChildFormset(self.request.POST, initial=itemNames, instance=self.object)
        context['futureexpenditure'] = FutureExpenditureFormset(self.request.POST, instance=self.object)
        context['previousexpenditure'] = PreviousExpenditureFormset(self.request.POST, instance=self.object)
        context['priorities'] = PriorityChildFormset(self.request.POST, initial=initial, instance=self.object)
    else:
        context['adoptedreviews'] = ReviewChildFormset(instance=self.object)
        context['estimatedbudget'] = EstimatedBudgetChildFormset(initial=itemNames, instance=self.object)
        context['futureexpenditure'] = FutureExpenditureFormset(instance=self.object)
        context['previousexpenditure'] = PreviousExpenditureFormset(instance=self.object)
        context['priorities'] = PriorityChildFormset(initial=initial, instance=self.object)
        
    return context

def form_valid(self, form):
    context = self.get_context_data()
    adoptedreview = context["adoptedreviews"]
    estimatedbudget = context["estimatedbudget"]
    prioritycriteria = context["priorities"]
    futureexpenditure = context["futureexpenditure"]
    previousexpenditure = context["previousexpenditure"]
    form.instance.creator = self.request.user
    if adoptedreview.is_valid() and estimatedbudget.is_valid() and previousexpenditure.is_valid() and futureexpenditure.is_valid and prioritycriteria.is_valid():

        self.object = form.save()
        adoptedreview.instance = self.object
        estimatedbudget.instance = self.object
        futureexpenditure.instance = self.object
        previousexpenditure.instance = self.object
        prioritycriteria.instance = self.object

        adoptedreview.save()
        estimatedbudget.save()
        previousexpenditure.save()
        futureexpenditure.save()
        prioritycriteria.save()
    else:
        return self.form_invalid(form)
    return super(ProjectCreateview, self).form_valid(form)

模板

 <form method="POST">
        {% csrf_token %}
        <fieldset class="form-group">
            <legend class="border-bottom mb-4">Update Project</legend>
            {{ form|crispy }}
        </fieldset>

        <h2>Adopted Budget Review</h2>
        <!-- {{ adoptedreviews|crispy }} -->
        <table class="table table-light">
          <thead>
            <tr>
              <th scope="col"></th>
              <th scope="col">BR1</th>
              <th scope="col">BR2</th>
              <th scope="col">BR3</th>
              <th scope="col">BR4</th>
            </tr>
          </thead>
          <tbody>
            <tr>
              <th scope="row">Amount</th>
              {{ adoptedreviews.management_form }}
              {% for adrform in adoptedreviews.forms %}
                 {% for field in adrform.visible_fields %}
                 <td>
                   {{ field.errors.as_ul }}
                   {{ field }}
                 </td>
                 {% endfor %}
              {% endfor %}
            </tr>
          </tbody>
        </table>

        <h2>Estimated Budget - Breakdown Yr1</h2>
        <!-- {{ estimatedbudget|crispy }} -->
        <table class="table table-light">
          {{ estimatedbudget.management_form }}
          <thead>
            <tr>
              <th scope="col">Item - Description</th>
              <th scope="col">Anticipated % cost</th>
              <th scope="col">Anticipated time - weeks</th>
            </tr>
          </thead>
          {% for form in estimatedbudget.forms %}
            <tr>
              <td>
                {{ form.errors }}
                <h4>{{ form.item.value }}</h4>
                {{ form.item.as_hidden }}
              </td>
              <td>
                {{ form.cost }}
              </td>
              <td>
                {{ form.time }}
              </td>
            </tr>
            {% endfor %}
         </table>

        <h2>Future Project Expenditure</h2>
        <!-- {{ futureexpenditure|crispy }} -->
        <table class="table table-light">
          {{ futureexpenditure.management_form }}
          {% for form in futureexpenditure.forms %}
              {% if forloop.first %}
                  <thead>
                  <tr>
                      {% for field in form.visible_fields %}
                          <th scope="col">{{ field.label|capfirst }}</th>
                      {% endfor %}
                  </tr>
                  </thead>

              {% endif %}
                <tbody>
                  <tr class="{% cycle row1 row2 %} formset_row">
                    {% for field in form.visible_fields %}
                        <td>
                            {# Include the hidden fields in the form #}
                            {% if forloop.first %}
                                {% for hidden in form.hidden_fields %}
                                    {{ hidden }}
                                {% endfor %}
                            {% endif %}
                            {{ field.errors.as_ul }}
                            {{ field }}
                        </td>
                    {% endfor %}
                  </tr>
                </tbody>
            {% endfor %}
         </table>

         <h2>Previous Project Expenditure</h2>
         <table class="table table-light">
           <!-- {{ previousexpenditure|crispy }} -->
          {{ previousexpenditure.management_form }}
          {% for form in previousexpenditure.forms %}
              {% if forloop.first %}
                  <thead>
                  <tr>
                      {% for field in form.visible_fields %}
                          <th scope="col">{{ field.label|capfirst }}</th>
                      {% endfor %}
                  </tr>
                  </thead>

              {% endif %}
                <tbody>
                  <tr class="{% cycle row1 row2 %} previous_formset_row">
                    {% for field in form.visible_fields %}
                        <td>
                            {# Include the hidden fields in the form #}
                            {% if forloop.first %}
                                {% for hidden in form.hidden_fields %}
                                    {{ hidden }}
                                {% endfor %}
                            {% endif %}
                            {{ field.errors.as_ul }}
                            {{ field }}
                        </td>
                    {% endfor %}
                  </tr>
                </tbody>
            {% endfor %}
         </table>

        <h2>Priority Criteria</h2>
        <!-- {{ priorities|crispy }} -->
        <table class="table table-light">
          <tbody>
            {{ priorities.management_form }}
            {% for priority in priorities.forms %}
            <tr>
              <td>
                {{ priority.priority.errors.as_ul }}
                {% for value, name in priority.priority.field.choices %}
                  {% if value == priority.priority.value %}
                    <h4>{{ name }}</h4>
                  {% endif %}
                {% endfor %}
                {{ priority.priority.as_hidden }}
              </td>
              <td>
                {{ priority.score.errors.as_ul }}
                {{ priority.score }}
              </td>
            </tr>
            {% endfor %}
          </tbody>
        </table>

        <div class="form-group">
            <button class="btn btn-outline-info" type="submit">Update Project</button>
        </div>
    </form>

无论何时手动呈现表单,都应该记住总是呈现它的隐藏字段,对于表单集/内联表单集更是如此,因为它自动向表单添加一些隐藏字段。由于 formset 正在更新多个实例,因此 formset 中当然需要有一些东西来指示 哪个 实例是哪个,这是由此处的隐藏字段完成的。

因此您需要呈现表单集表单的隐藏字段:

<form method="POST">
        {% csrf_token %}
        <fieldset class="form-group">
            <legend class="border-bottom mb-4">Update Project</legend>
            {{ form|crispy }}
        </fieldset>

        <h2>Adopted Budget Review</h2>
        <!-- {{ adoptedreviews|crispy }} -->
        <table class="table table-light">
          <thead>
            <tr>
              <th scope="col"></th>
              <th scope="col">BR1</th>
              <th scope="col">BR2</th>
              <th scope="col">BR3</th>
              <th scope="col">BR4</th>
            </tr>
          </thead>
          <tbody>
            <tr>
              <th scope="row">Amount</th>
              {{ adoptedreviews.management_form }}
              {% for adrform in adoptedreviews.forms %}
                 {% for hidden in adrform.hidden_fields %}
                     {{ hidden }}
                 {% endfor %}
                 {% for field in adrform.visible_fields %}
                 <td>
                   {{ field.errors.as_ul }}
                   {{ field }}
                 </td>
                 {% endfor %}
              {% endfor %}
            </tr>
          </tbody>
        </table>

        <h2>Estimated Budget - Breakdown Yr1</h2>
        <!-- {{ estimatedbudget|crispy }} -->
        <table class="table table-light">
          {{ estimatedbudget.management_form }}
          <thead>
            <tr>
              <th scope="col">Item - Description</th>
              <th scope="col">Anticipated % cost</th>
              <th scope="col">Anticipated time - weeks</th>
            </tr>
          </thead>
          {% for form in estimatedbudget.forms %}
            {% for hidden in form.hidden_fields %}
                {{ hidden }}
            {% endfor %}
            <tr>
              <td>
                {{ form.errors }}
                <h4>{{ form.item.value }}</h4>
                {{ form.item.as_hidden }}
              </td>
              <td>
                {{ form.cost }}
              </td>
              <td>
                {{ form.time }}
              </td>
            </tr>
            {% endfor %}
         </table>

        <h2>Future Project Expenditure</h2>
        <!-- {{ futureexpenditure|crispy }} -->
        <table class="table table-light">
          {{ futureexpenditure.management_form }}
          {% for form in futureexpenditure.forms %}
              {% if forloop.first %}
                  <thead>
                  <tr>
                      {% for field in form.visible_fields %}
                          <th scope="col">{{ field.label|capfirst }}</th>
                      {% endfor %}
                  </tr>
                  </thead>

              {% endif %}
                <tbody>
                  <tr class="{% cycle row1 row2 %} formset_row">
                    {% for hidden in form.hidden_fields %}
                        {{ hidden }}
                    {% endfor %}
                    {% for field in form.visible_fields %}
                        <td>
                            {# Include the hidden fields in the form #}
                            {% if forloop.first %}
                                {% for hidden in form.hidden_fields %}
                                    {{ hidden }}
                                {% endfor %}
                            {% endif %}
                            {{ field.errors.as_ul }}
                            {{ field }}
                        </td>
                    {% endfor %}
                  </tr>
                </tbody>
            {% endfor %}
         </table>

         <h2>Previous Project Expenditure</h2>
         <table class="table table-light">
           <!-- {{ previousexpenditure|crispy }} -->
          {{ previousexpenditure.management_form }}
          {% for form in previousexpenditure.forms %}
              {% if forloop.first %}
                  <thead>
                  <tr>
                      {% for field in form.visible_fields %}
                          <th scope="col">{{ field.label|capfirst }}</th>
                      {% endfor %}
                  </tr>
                  </thead>

              {% endif %}
                <tbody>
                  <tr class="{% cycle row1 row2 %} previous_formset_row">
                    {% for hidden in form.hidden_fields %}
                        {{ hidden }}
                    {% endfor %}
                    {% for field in form.visible_fields %}
                        <td>
                            {# Include the hidden fields in the form #}
                            {% if forloop.first %}
                                {% for hidden in form.hidden_fields %}
                                    {{ hidden }}
                                {% endfor %}
                            {% endif %}
                            {{ field.errors.as_ul }}
                            {{ field }}
                        </td>
                    {% endfor %}
                  </tr>
                </tbody>
            {% endfor %}
         </table>

        <h2>Priority Criteria</h2>
        <!-- {{ priorities|crispy }} -->
        <table class="table table-light">
          <tbody>
            {{ priorities.management_form }}
            {% for priority in priorities.forms %}
            {% for hidden in priority.hidden_fields %}
                {{ hidden }}
            {% endfor %}
            <tr>
              <td>
                {{ priority.priority.errors.as_ul }}
                {% for value, name in priority.priority.field.choices %}
                  {% if value == priority.priority.value %}
                    <h4>{{ name }}</h4>
                  {% endif %}
                {% endfor %}
                {{ priority.priority.as_hidden }}
              </td>
              <td>
                {{ priority.score.errors.as_ul }}
                {{ priority.score }}
              </td>
            </tr>
            {% endfor %}
          </tbody>
        </table>

        <div class="form-group">
            <button class="btn btn-outline-info" type="submit">Update Project</button>
        </div>
    </form>

此外,由于您有多个表单集,您应该为它们提供一个前缀以提供名称冲突(参见 Using more than one formset in a view):

if self.request.POST:
    context['adoptedreviews'] = ReviewChildFormset(self.request.POST, instance=self.object, prefix='adoptedreviews_form')
    context['estimatedbudget'] = EstimatedBudgetChildFormset(self.request.POST, initial=itemNames, instance=self.object, prefix='estimatedbudget_form')
    context['futureexpenditure'] = FutureExpenditureFormset(self.request.POST, instance=self.object, prefix='futureexpenditure_form')
    context['previousexpenditure'] = PreviousExpenditureFormset(self.request.POST, instance=self.object, prefix='previousexpenditure_form')
    context['priorities'] = PriorityChildFormset(self.request.POST, initial=initial, instance=self.object, prefix='priorities_form')
else:
    context['adoptedreviews'] = ReviewChildFormset(instance=self.object, prefix='adoptedreviews_form')
    context['estimatedbudget'] = EstimatedBudgetChildFormset(initial=itemNames, instance=self.object, prefix='estimatedbudget_form')
    context['futureexpenditure'] = FutureExpenditureFormset(instance=self.object, prefix='futureexpenditure_form')
    context['previousexpenditure'] = PreviousExpenditureFormset(instance=self.object, prefix='previousexpenditure_form')
    context['priorities'] = PriorityChildFormset(initial=initial, instance=self.object, prefix='priorities_form')

Note: You seem to have weird HTML (multiple tbody elements in a table??), and also you might want to reconsider having so many formsets in a page, this can be considered bad for UX purposes (This can be really confusing for the user, not to mention the developer too). One page should ideally serve one purpose to the user.