Overriding delete method in generic delete view not working and getting warning

I have a model for presets

class Preset(models.Model):
     id = models.UUIDField(default=uuid.uuid4, unique=True, editable=False, primary_key=True)
     user = models.ForeignKey('accounts.CustomUser', on_delete=models.PROTECT)
     user_provided_name = models.CharField(max_length=100, null=False, blank=False) 
     archived = models.BooleanField(default=False)

The presets are referenced as a FK in the Session model:

class Session(models.Model):
     id = models.UUIDField(default=uuid.uuid4, unique=True, editable=False, primary_key=True)
     user = models.ForeignKey('accounts.CustomUser', on_delete=models.PROTECT)
     user_provided_name = models.CharField(max_length=150, null=False, blank=False)
     preset = models.ForeignKey(CropPreset, on_delete=models.PROTECT)

I am using the generic DeleteView. When a user tries to delete a preset but it has been used in the Session model, I want it to not delete, but just mark it as archived.

Here is my current DeleteView:

class PresetDeleteView(LoginRequiredMixin, DeleteView):
    def get_success_url(self):
        messages.success(self.request, "Your preset has been deleted")
        return reverse('presets_list')
    
    def get_queryset(self):
        return Preset.objects.filter(user=self.request.user)

    def delete(self, *args, **kwargs):
        try:
            return super(PresetDeleteView, self).delete(*args, **kwargs)
        except models.ProtectedError as e:
            self.object = self.get_object()
            self.object.archived=True
            self.object.save()
            return HttpResponseRedirect(self.get_success_url)

When I try this view with a preset with no attached session, it deletes the record just fine but I get this warning in the command line:

DeleteViewCustomDeleteWarning: DeleteView uses FormMixin to handle POST requests. As a consequence, any custom deletion logic in PresetDeleteView.delete() handler should be moved to form_valid().
  self = cls(**initkwargs)

When I try to run the view with a preset that has records attached and mark it as archived, it just gives me this on the Django debug screen

ProtectedError at /presets/delete/<whatever the uuid is here>/

2 questions:
What does the DeleteViewCustomDeleteWarning mean?
How do I get it to actually execute the except statement in the delete method I am overriding?

See DeleteView -- Classy CBV and DeleteView to get a detailed view of what a DeleteView is doing.

Briefly, DeleteView receives a POST request, not a DELETE request.

The post method in BaseDeleteView does not call self.delete, it calls self.form_valid. Then, self.form_valid calls self.object.delete before returning the redirect to success_url.

So, in a “normal” POST-based form submission, self.delete in the view does not get called. An HTML form may only submit a get or post command.
(It would get called if it was a rest-style submission using AJAX with a delete.)

The warning message is telling you what you need to do. The logic that you’re putting in the delete method needs to be in form_valid if you want it to run.

2 Likes

I found your solution via search, and am having the same issue.
I did as you said and moved my code to the form_valid method.

But now, when I click the delete button, I see 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>

Any ideas why the error is saying it didn’t return an HttpResponse object?

First, a side note - this topic has been marked as solved. It would have been appropriate for you to have opened a new topic for your issue, especially since your error is not related to the original post.

Yes. You have a sequence of code that doesn’t result in you returning an HttpResponse object.

What could be happening in your code to cause that?

Okay, you are correct, and I apologize for jumping on this thread. I thought my issue related to the original post, but upon re-reading the post, this is not an appropriate place for my question.

I’ve created a new one here: