UpdateView with form_class removes named submit input from POST

Viewed 84

I want to know which submit input was clicked by the user. The submitted form goes to an UpdateView that uses a ModelForm. I've added the name attribute to the submit inputs but when I do if 'approve' in self.request.POST: within get_context_data() it's False. I overridden def post() and approve is a key in the POST dict. Is there a way to get approve or decline to show in get_context_data()?.

At a guess it has to be something to do with the ModelForm being involved but is there a way to get it through without overriding the post method or should I just except it?

FYI, I've omitted some code that was just bloat and not relevant to the issue. Apologies if there's something in there that doesn't make sense.

models.py

class Definition(DefinitionInfo):
    SEV3 = 'info'
    SEV2 = 'warning'
    SEV1 = 'danger'
    sup_sevs = (
        ('', 'Please select'),
        (SEV3, 'Sev3'),
        (SEV2, 'Sev2'),
        (SEV1, 'Sev1'),
    )
    title = models.CharField(max_length=255)
    description = models.TextField(null=True, blank=True)
    resolution = models.TextField(null=True, blank=True)
    sup_sev = models.CharField(max_length=7, choices=sup_sevs, null=False, blank=False)

forms.py

class DefinitionForm(forms.ModelForm):
    class Meta:
        model = Definition
        fields = ['title', 'sup_sev', 'description', 'resolution']
        widgets = {
            'description': forms.HiddenInput(),
            'resolution': forms.HiddenInput(),
        }

views.py

class DefinitionUpdate(LoginRequiredMixin, UpdateView):
    model = Definition
    context_object_name = 'definition'
    form_class = DefinitionForm
    template_name_suffix = '_form'
    success_url = reverse_lazy('definitions:index')

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        if self.request.method == 'POST':
            print(f'Post data: {self.request.POST}')
        return context

update_form.html

<form id="defForm" class="container-sm w-50" enctype="multipart/form-data" method="post" action=".">
  {% csrf_token %} {{ form.delete }}
  <div class="row g-3">
    <div class="col-md-10">
      <div class="form-floating mb-3">
        <input type="text" class="form-control form-control-sm bg-dark text-light" id="{{ form.title.auto_id }}" name="{{ form.title.html_name }}" placeholder="Enter title here..." value="{{ form.title.value|default_if_none:'' }}">
        <label class="form-label" for="{{ form.title.id_for_label }}">Title</label>
      </div>
    </div>
    <div class="col-sm">
      <div class="form-floating mb-3">
        {{ form.sup_sev }}
        <label class="form-label" for="{{ form.sup_sev.id_for_label }}">Severity</label>
      </div>
    </div>
  </div>
  <div class="form-floating mb-3">
    {{ form.description }}
    <div id="description" class="form-control form-control-sm bg-dark text-light d-inline-block" style="white-space: pre-wrap;" contentEditable>{{ form.description.value|default_if_none:'' }}</div>
    <label class="form-label" for="{{ form.description.id_for_label }}">Description</label>
  </div>
  <div class="form-floating mb-3">
    {{ form.resolution }}
    <div id="resolution" class="form-control form-control-sm bg-dark text-light d-inline-block" style="white-space: pre-wrap;" contentEditable>{{ form.resolution.value|default_if_none:'' }}</div>
    <label class="form-label" for="resolution">Resolution</label>
  </div>
  <div class="d-grid gap-2 d-md-flex justify-content-md-end">
    <input id="decline" class="btn btn-outline-danger" name="decline" type="submit" value="Decline">
    <input id="approve" class="btn btn-outline-success" name="approve" type="submit" value="Approve">
  </div>
</form>

1 Answers

So after time away from it I've resolved it.

First I found that I had a Jquery script that was submitting the form with $(this).submit();. That, of course, means that it's not the submit input submitting it. So by simply removing that and preventDefault(); it worked. The below is the culprit. It copies over the text to the relative hidden input. (It's so I have a text area that resizes automatically but still get the info to the server)

$(document).ready(function() {
    let form = $('#defForm')
    let descDiv = $("#description");
    let resoDiv = $('#resolution');
    form.one('submit', function(e) {
        // e.preventDefault();
        let descTa = $("#id_description");
        let resoTa = $("#id_resolution");
        descTa.val(descDiv.text());
        resoTa.val(resoDiv.text());
        //$(this).submit();
    });
});

The second reason was that I needed a little extra configuration in the form.

I've added 2 fields to the ModelForm called approve and decline. Each are a CharField that aren't required and include a TextInput widget of which the attrs has {'type': 'submit', 'value': 'Approve'} (not exactly needed but good for if you just use {{ form.approve }} in the template).

Doing it this way not only shows the clicked submit in self.request.POST within get_context_data() but also the more useful form_valid().

class DefinitionForm(forms.ModelForm):
    approve = forms.CharField(widget=forms.TextInput(attrs={'type': 'submit', 'value': 'Approve'}), required=False)
    decline = forms.CharField(widget=forms.TextInput(attrs={'type': 'submit', 'value': 'Decline'}), required=False)
    
    class Meta:
        model = Definition
        fields = ['title', 'sup_sev', 'description', 'resolution']
        widgets = {
            'description': forms.HiddenInput(),
            'resolution': forms.HiddenInput(),
        }
Related