DeleteView form_valid is requiring field

I’m having an issue that popped up with a Class Based View’s generic.DeleteView.
When I attempt to delete an instance of an object, I get the following error:

The view clients.views.view didn’t return an HttpResponse object. It returned None instead.

This is my delete class:

class CSNDelete(UserAccessMixin, SuccessMessageMixin, generic.DeleteView):
    permission_required = 'clients.delete_csn'

    model = CSN
    form_class = CSNForm
    template_name = 'generic_delete.html'
    success_message = model._meta.verbose_name + " deleted successfully."

    def get_context_data(self, **kwargs):
        # Call the base implementation first to get a context
        context = super(CSNDelete, self).get_context_data(**kwargs)
        # Grab the single client id passed in from urls.py
        client_id = self.kwargs['client_id']
        context['c_id'] = client_id
        context['client_full_name'] = get_object_or_404(Client, id=client_id)
        return context

    def form_valid(self, form):
        obj = self.get_object()
        messages.success(self.request, self.success_message % obj.__dict__)
        return super(CSNDelete, self).delete(request, *args, **kwargs)

    def form_invalid(self, form):
        print('==> ERRORS: ' + str(form.errors))

    # def delete(self, request, *args, **kwargs):
    #     obj = self.get_object()
    #     messages.success(self.request, self.success_message % obj.__dict__)
    #     return super(CSNDelete, self).delete(request, *args, **kwargs)

    def get_success_url(self):
        return reverse('clients:detail', args=[str(self.object.client.pk)])

And the heart of my delete template:

{% load crispy_forms_tags %}
        <form action="" method="POST">
            {% csrf_token %}

            <h4 class="my-3">Are you sure you want to permanently delete "{{ object }}" from {{ client_full_name }}?</h4>

            <button class="btn btn-danger">DELETE</button>
            <a href="{{ view.get_success_url }}" class="btn btn-light">Cancel</a>
        </form>

I was previously using the commented out “delete” method in the DeleteView, but saw on another post that I should be using the “form_valid” method.

I believe that the “form_valid” method is now causing another error when I try to delete the instance, saying that a specific field is required.

The field that it is saying is required looks like this in the models.py file:

goals = models.ManyToManyField("Obj2", verbose_name="Goals")

This is a new field that I added recently, after the original instance I’m trying to delete was created. Is that why this is having an issue? What is the best way to resolve this? Put some sort of default value on that ManyToManyField?
Or would I just need edit every instance that needs to be deleted and give it a value in that “goals” field first?
Or is there an if/else that needs to go in the form_valid method to “ignore” validation when using “Delete”?

This specific error has nothing to do with any of the possibilities you’re asking about.

When this view is being executed, the is_valid function is called to determine whether or not the form is valid.

If the submitted form is valid, form_valid gets called.

If the submitted form is invalid, form_invalid is called.

One of these two is not returning an HttpResponse object from that function.

1 Like

Thanks for the response.

Did you see my other comments I added (when I created this as a new topic) about the “required” field on that object?

Yes, but that’s a different issue (I’ll get to that below).

The specific problem you’re asking about:

is caused by your form_invalid method not returning an HttpResponse object. It has nothing to do with your form_valid method. Making any changes, or doing anything to modify form_valid is not going to resolve that issue, because form_valid isn’t getting called.

Now, regarding your supplemental questions - the DeleteView is functionally different than the other editing views. The “form” being referenced in the view is not supposed to be an “edit” form. It’s intended to be a “confirmation form” - it should not have all the requirements of a model form.

You’re not even rendering that form in your template.

Review the docs and example at Generic editing views | Django documentation | Django

Okay, I’ve reviewed the link you sent and cannot tell any reason why my form_invalid would not be returning an HttpResponse object.

Do you have any suggestions to troubleshoot what is happening there?
I thought putting in the following would help:
print('==> ERRORS: ' + str(form.errors))

But that only reveals the following error in the console:

==> ERRORS: <ul class="errorlist"><li>service_date<ul class="errorlist">
<li>This field is required.</li></ul></li><li>goals
<ul class="errorlist"><li>This field is required.</li></ul></li></ul>

Any thing else I should try to do to debug this?

What are you returning from form_invalid?

Okay, I updated my form_invalid to the following…

    def form_invalid(self, form):
        print('==> ERRORS: ' + str(form.errors))
        messages.error(self.request, mark_safe('CSN NOT DELETED.<br/>'
                                               'Error: ' + str(form.errors)))
        return super(CSNDelete, self).form_invalid(form)

The result looks like this:

CSN NOT DELETED.
Error:

  • service_date
    • This field is required.
  • goals
    • This field is required.

So, I’m still not sure why my DeleteView is trying to check for required fields.

What form are you using in that view?

Here is the view:

class CSNDelete(UserAccessMixin, SuccessMessageMixin, generic.DeleteView):
    permission_required = 'clients.delete_csn'

    model = CSN
    form_class = CSNForm
    template_name = 'generic_delete.html'
    success_message = model._meta.verbose_name + " deleted successfully."

    def get_context_data(self, **kwargs):
        # Call the base implementation first to get a context
        context = super(CSNDelete, self).get_context_data(**kwargs)
        # Grab the single client id passed in from urls.py
        client_id = self.kwargs['client_id']
        context['c_id'] = client_id
        context['client_full_name'] = get_object_or_404(Client, id=client_id)
        return context

    def form_valid(self, request, *args, **kwargs):
        print('==========> IN HERE 1');
        obj = self.get_object()
        messages.success(self.request, self.success_message % obj.__dict__)
        return super(CSNDelete, self).delete(request, *args, **kwargs)

    def form_invalid(self, form):
        print('==> ERRORS: ' + str(form.errors))
        # response = super().form_invalid(form)
        messages.error(self.request, mark_safe('CSN NOT DELETED.<br/>'
                                               'Error: ' + str(form.errors)))
        return super(CSNDelete, self).form_invalid(form)

    def get_success_url(self):
        return reverse('clients:detail', args=[str(self.object.client.pk)])

Reading into what you said, I just removed this line:
form_class = CSNForm

Then I was able to successfully delete!

So, why is this?
This code has worked fine for a while.
Is it because of an upgrade from Django 3.1 to v4? And I just didn’t notice because I hadn’t deleted anything in a while?

See the second half of my earlier response #4.

Whenever in doubt about what these generic views are doing, read the source! (And, keep your references to Classy Class Based Views and CBV diagrams handy.)

Assuming this view, model, and template has remained essentially unchanged since your Django 3 version, yes, the upgrade would have revealed these problems that had been masked by the way this view was previously implemented.

See the Django 4 release notes

Thank you, as always, for your help!