Search code examples
djangoformsinline-formset

Using two submit buttons to save django inlineformset as draft


I have an inlineformset that works. I have attempted to add a second submit button, that when clicked will check that a particular field in each inline form has been filled out i.e. this field becomes a required filed only when the second submit button is clicked.

The problem is that when I click this second submit button the validation errors don't appear and the form just seems to submit anyway. I think the issue is within my view, within form_valid. I'm not sure what I need to return when if form.is_valid() fails.

I'm teaching myself to code, so any help or direction is much appreciated.

Forms.py

class response_form_draft(forms.ModelForm):
    class Meta:
        name = response
        exclude = ['id_question','order']
    def __init__(self, *args, **kwargs):
        super(response_form_draft, self).__init__(*args, **kwargs)
        self.helper = FormHelper()
        self.helper.form_show_labels = False

class response_form_final(forms.ModelForm):
    class Meta:
        name = response
        exclude = ['id_question','order']
    def __init__(self, *args, **kwargs):
        super(response_form_final, self).__init__(*args, **kwargs)
        self.helper = FormHelper()
        self.helper.form_show_labels = False
    def clean(self):
        data = self.cleaned_data
        resp = data['response']
        if resp == "":
            raise forms.ValidationError(('must not be blank'))


checklist_formset_draft = inlineformset_factory(checklist, response,
                                        form = response_form_draft,
                                        extra=0, can_delete=False,
                                        widgets={'comments': forms.Textarea(attrs={'cols': 7, 'rows': 3,
                                                                                    'style':'resize:none'})
                                                                                    })

checklist_formset_final = inlineformset_factory(checklist, response,
                                        form = response_form_final,
                                        extra=0, can_delete=False,
                                        widgets={'comments': forms.Textarea(attrs={'cols': 7, 'rows': 3,
                                                                                    'style':'resize:none'}),
                                                'response': forms.TextInput(attrs={'required':True})
                                                })

Views.py

class ChecklistUpdateView(LoginRequiredMixin, UpdateView):
    login_url = '/user/login'
    model = checklist
    form_class = checklist_form
    success_url = reverse_lazy('checklist:checklist_list')

    def get_context_data(self, **kwargs):
        data = super(ChecklistUpdateView, self).get_context_data(**kwargs)
        if self.request.POST:
            if 'complete' in self.request.POST:
                print("complete")
                data['question'] = checklist_formset_final(self.request.POST, instance=self.object)
            else:
                print("draft")
                data['question'] = checklist_formset_draft(self.request.POST, instance=self.object)
        else:
            data['question'] = checklist_formset_draft(instance=self.object)
        return data

    def form_valid(self, form):
        context = self.get_context_data()
        question = context['question']
        with transaction.atomic():
            self.object = form.save(commit=False)
            self.object.last_edit_by = str(self.request.user)
            self.object.last_edit_date = timezone.now()
            if question.is_valid():
                question.instance = self.object
                question.save()

        return super(ChecklistUpdateView, self).form_valid(form)

HTML template

{% extends "base.html" %}
{% load crispy_forms_tags %}
{% block content %}


<div class="container">

<form method="post">
  {% csrf_token %}
  <h1>{{ object.id_template.title }}</h1>
  {{ form.errors }}
  {{ form.entity|as_crispy_field }}

  {{ form.date_created.as_hidden }}
  {{ form.created_by.as_hidden }}
  {{ form.id_template.as_hidden }}
  {{ form.status.as_hidden }}
<div class="card">


<table id="table_id" class="table">
  <tbody>
    {{ question.management_form }}

    {% for form in question.forms %}
        {{ formset.errors }}
        {{ form.non_field_errors }}
      {% if forloop.first %}
          <thead class="thead-dark">
          <tr>
              {% for field in form.visible_fields %}
                  <th>{{ field.label|capfirst }}</th>
              {% endfor %}
              <th></th>
          </tr>
          </thead>
      {% endif %}

      <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 }}
                            {% if field.label == "Question" %}
                              <p>{{ form.question.value }}</p>
                              {{ field.as_hidden }}
                            {% elif field.label == "Response" %}
                              {{ field.as_hidden }}
                                <button id="Yes.{{ forloop.parentloop.counter0 }}" row="{{ forloop.parentloop.counter0 }}" class="ans {{ forloop.parentloop.counter0 }} btn btn-dark right">Yes</button>
                                <button id="No.{{ forloop.parentloop.counter0 }}" row="{{ forloop.parentloop.counter0 }}" class="ans {{ forloop.parentloop.counter0 }} btn btn-dark right">No</button>
                                <button id="N/a.{{ forloop.parentloop.counter0 }}" row="{{ forloop.parentloop.counter0 }}" class="ans {{ forloop.parentloop.counter0 }} btn btn-dark right">N/a</button>


                            {% else %}
                            {{ field|as_crispy_field }}
                            {% endif %}
                        </td>
                    {% endfor %}
                    <td>  </td>
                </tr>

{% endfor %}
</tbody>
</table>
</div>
{{ form.comments|as_crispy_field }}
  {{ form.audit|as_crispy_field }}
  <div class="form-submit-row" id="submit-row">
    <input class="btn btn-dark right" name="draft "type="submit" value="Save draft">
    <input class="btn btn-dark right" name="complete" type="submit" value="Complete">
    <a href="{% url 'checklist:checklist_delete' pk=object.id %}" class="btn btn-danger right">Delete</a>
  </div>
</form>
<br><br>

</div>


Solution

  • form_valid is activated when your form is valid. In your classview django automatically check validation for your checklist_form without validating formset. you can add custom validation in your form_valid and form_invalid, but i would go another way. i would put formset inside your base form (checklist_form)

    something like that:

    class checklist_form(forms.ModelForm):
        def __init__(*args, **kwargs):
           self.question= kwargs.pop("question")
           super()__.init__(*args, **kwargs)
    
        def clean(self):
           cleaned_data = super().clean()
           if not self.question.is_valid():
              self.add_error(None, "Have some formset errors")
           return cleaned_data
    

    now form will be valid only if formset is also valid. And then in your ChecklistUpdateView i would put creation of formset into get_form_kwargs

        def get_form_kwargs(self):
            data = super().get_form_kwargs()
            if self.request.POST:
                if 'complete' in self.request.POST:
                    data['question'] = checklist_formset_final(self.request.POST, instance=self.object)
                else:
                    data['question'] = checklist_formset_draft(self.request.POST, instance=self.object)
            else:
                data['question'] = checklist_formset_draft(instance=self.object)
            return data
    

    After that in your html template you will need to use form.question instead of question