Marc
Marc

Reputation: 167

DeleteView with confirmation template and POST method

I thought I am doing a standard operation when trying to delete several objects of a model at once with a DeleteView and the POST method, having a confirmation template in between.

Well, either I am sitting too long in front of the computer or this supposedly standard way is not directly supported by Django.

As Djangos docs and other posts say, a confirmation template is only shown (with a Confirmation button) if I use a GET method. But in my case I have a ListView with all my objects of my model and the first column being the checkboxes whose values correspond to the respective objects' IDs. I can check several checkboxes at once and then click on my "Delete selected items". The form uses the POST method (I don't want to use GET).

Now: Getting a DeleteView to work with a confirmation template in case of a POST method seems to cause a little of a hack. I did not succeed after several hours.

What is the best way here?

The header of my class looks like this:

class SomeItemConfirmDeleteView(DeleteView):
    template_name = 'confirm_delete_someitems.html'
    model = SomeItem
    success_url = reverse_lazy('list_someitems_url')
    items_to_delete = []

Steps done so far:

  1. I overwrote the POST method so that if I have a list with all IDs to be deleted coming from the checked checkboxes, I call the GET method so that the confirmation template will be shown. Otherwise, the "Confirm deletion" button has been pressed in the confirm_delete_someitems.html.

    def post(self, request, *args, **kwargs):
        self.items_to_delete = self.request.POST.getlist('itemsToDelete')
        if not self.items_to_delete:
            return self.delete(request, *args, **kwargs)
        else:
            return self.get(self, *args, **kwargs)
    
  2. I overwrote the get_object method

    def get_object(self, queryset=None):
        return self.get_queryset()
    
  3. I overwrote the get_queryset method so that the objects to be deleted are displayed in a list in the confirm_delete_someitems.html. I get the objects from the checked checkboxes which are named "itemsToDelete".

    def get_queryset(self):
        if not self.items_to_delete:
            queryset = super(ChargeParkConfirmDeleteView, self).get_queryset()
            self.queryset = # ... HOW TO GET THE OBJECTS TO DELETE??
            return self.queryset
        else:
            queryset = super(SomeItemConfirmDeleteView, self).get_queryset()
            self.queryset = queryset.filter(id__in=self.items_to_delete)
            return self.queryset
    

The last step is where it gets tricky: how do I get the objects which are to be deleted from the form of confirm_delete_someitems.html:

<form action="" method="post">
        {% csrf_token %}
        {% trans 'The following objects as well as their related objects will be deleted. Are you sure?' %}
        <ul>
            {% for item in object %}
                <li>{{ item }}</li>
            {% endfor %}
        </ul>
        <input type="submit" value="{% trans 'Confirm deletion' %}" />
    </form>

Upvotes: 3

Views: 12285

Answers (3)

MDoe
MDoe

Reputation: 193

Thanks Marc

This was really helpful for me. I also wanted to get to a few generic class based views like DeleteView via post method. However, I had to change a few things and I want to add this here, if someone else with a limited knowledge of Python as me would search for a solution.

Part of my html template:

    <form method="post">{% csrf_token %}
                        <div class="form-group">
                                Sure, you want to delete that object?<br>
                                <br>
                                <strong>{{ object }}</strong>
                                {{ form.errors }}
                                <br>
                                <br>
                                <input type="hidden" name="confirm_delete" value="confirm_delete">
                                <button type="submit" class="btn btn-primary">Delete</button>
                        </div>
                </form>

and part of my views.py

class MV_Loeschen(DeleteView):
    template_name = templ_folder_name + 'mv_loeschen.html'
    model = MV
    success_url = reverse_lazy(url_app_name + 'mv_ausgabe_alle')

    def get_object(self, queryset=None):
        self.queryset = MV.objects.get(mv_id = self.to_delete)
        return self.queryset

    def post(self, request, mvpk):
        self.to_delete = mvpk
        if self.request.POST.get("confirm_delete"):
            queryset = MV.objects.get(mv_id = mvpk)
            queryset.delete()
            return HttpResponseRedirect(self.success_url)
        else:
            return self.get(self, mvpk)

So, what am I doing here? The def post() method explains to MV_Loeschen how to handle post requests. I receive a parameter mvpk from another page via post form where I tell Django that I want to go to a view in order to delete the MV object with id that is in mvpk.

Then I set self.to_delete to contain that id (mvpk). I need that later in another def. Then I have to see if I just reached the confirmation page (do you really want to delete that?) or if I already confirmed that I want to delete the object. The first case is handled by the else fork (or however you call that) at the the end. This seems to raise internally a call for the DeleteView, but using the GET method. So I looked this up at

https://docs.djangoproject.com/en/3.0/ref/class-based-views/generic-editing/#django.views.generic.edit.DeleteView

and also the code in my python/site-packages/subfolders.

The def get in turn uses the get_object method. But this def get_object is unaware of mvpk/the id of my MV object. So, I get this from self.to_delete (therefore I needed to assign that before). Here I take a different approach than Marc.

As in his solution get_object() calls get_queryset(), which returns a queryset, and I did not understand the super-line in his solution, I just get the queryset directly here in get_object().

So back to def post(). In case I already confirmed that I want to delete that object, have to get that parameter. I do this with:

self.request.POST.get("confirm-delete")

Thanks again, Marc, I was wondering why I could not access:

request.POST['confirm-delete']

directly. I am not sure that I understood that entirely by now, but at least you helped me to get this working.

Then I might have a redundant line with the new setting of the queryset. I don't know. And then I delete the queryset or the MV object respectively.

Finally I redirect to the success_url and had to import HttpResponseRedirect from django.http for that.

Upvotes: 1

Pooja Vadariya
Pooja Vadariya

Reputation: 11

$(".delete-link").click(function(){
    confirm_box = confirm("Are you sure?");
    if(confirm_box == true)
        return true;
    else
        return false;
})

Upvotes: 1

Marc
Marc

Reputation: 167

I now found a solution: I basically add hidden input fields which again transport the IDs of the checked checkboxes on the POST request.

But if someone finds an even more elegant way, I'm open to suggestions. :)

views.py

class SomeItemConfirmDeleteView(DeleteView):
    template_name = 'confirm_delete_someitems.html'
    model = SomeItem
    success_url = reverse_lazy('list_someitems_url')
    items_to_delete = []

    def get_queryset(self):
        queryset = super(ChargeParkConfirmDeleteView, self).get_queryset()
        self.queryset = queryset.filter(id__in=self.items_to_delete)
        return self.queryset

    def get_object(self, queryset=None):
        return self.get_queryset()

    def post(self, request, *args, **kwargs):
        self.items_to_delete = self.request.POST.getlist('itemsToDelete')
        if self.request.POST.get("confirm_delete"):
            # when confirmation page has been displayed and confirm button pressed
            queryset = self.get_queryset()
            queryset.delete() # deleting on the queryset is more efficient than on the model object
            return HttpResponseRedirect(self.success_url)
        elif self.request.POST.get("cancel"):
            # when confirmation page has been displayed and cancel button pressed
            return HttpResponseRedirect(self.success_url)
        else:
            # when data is coming from the form which lists all items
            return self.get(self, *args, **kwargs)

confirm_delete_someitems.html:

<form action="" method="post">
    {% csrf_token %}
    {% trans 'The following objects as well as their related objects will be deleted. Are you sure?' %}
    <ul>
        {% for item in object %}
            <input type="hidden" value="{{ item.id }}" name="itemsToDelete" />
            <li><a href="{{ item.get_absolute_url }}">{{ item }}</a></li>
        {% endfor %}
    </ul>
    <input type="submit" class="btn btn-primary" value="{% trans 'Confirm deletion' %}" name="confirm_delete" />
    <input type="submit" class="btn btn-primary" value="{% trans 'Cancel' %}" name="cancel"/>
</form>

Upvotes: 3

Related Questions