简体   繁体   中英

How to dynamically delete object using django formset

Django says, i should render inline formset this way:

{{ formset.management_form }}
{% for form in formset %}
    {{ form.id }}
    {{ form.field_1 }} 
    {{ form.field_2 }}
    <button type="button"> delete </button>
{% endfor %}
<button type="submit"> submit </button>

Ok. But what if i want to delete some formset objects ( form ) dynamically? User press delete button - i remove form from the DOM, i use ajax to remove object, related to the form from the DATABASE. It works ok till this point. But when user clicks submit - my views.py trying to validate formset:

filled_formset = OrderItemFormSet(request.POST, instance=order)
if filled_formset.is_valid():

and raises error:

MultiValueDictKeyError at /order/cart/  
"'orderitem_set-0-id'"  
...\market\ordersys\views.py in show_cart  
59.   if filled_formset.is_valid():

I think it happend because form objects were displayed by django with some regularity (first form got id = orderitem_set-0-id , second = orderitem_set-1-id etc.) And when i deleted first form from the DOM, the regularity was broken - there is no more form with orderitem_set-0-id . But is_valid() still trying to get it dict["orderitem_set-0-id"] .

I could use some black magic, substituting django's technical information, displayed in the DOM, restoring disrupted regularity, but is there a better way?

Could you tell me how to properly dynamically delete formset items, please ?

I got no answer for some time, so i did not find any better solution than below. Maybe someome will find it useful.

Ok, the trick is - to have {{form.DELETE}} for any form in your formset in template. It renders as a checkbox (i made it invisible) and i made JS to make it "checked" whenever user press "delete" button. After user press "submit" button, every form , marked for deletion, will NOT be validated by the view during filled_formset.is_valid() . This lets you delete object from database with ajax behind the scene.

The problem was that an ERROR was raised during formset validation. Caused by the form of an object, which was already deleted from database with ajax.

So there are all components:

views.py

def show_cart(request):
    OrderItemFormSet = inlineformset_factory(Order, OrderItem, form=OrderItemForm, extra=0, can_delete=True)
    order = Order.objects.get(pk=request.session['order'])

    if request.method == 'GET':                                                                  
        formset = OrderItemFormSet(instance=order)
        return render(request, 'ordersys/cart.html', {'formset': formset})

    elif request.method == 'POST':            
        filled_formset = OrderItemFormSet(request.POST, instance=order)
        if filled_formset.is_valid():                
            filled_formset.save()
            return redirect('catalog:index')
        else:
            return render(request, 'ordersys/cart.html', {'formset': filled_formset})

cart.html

<form action="" method="post">
    {{ formset.management_form }}
    {% for form in formset %}
        {{ form.id }}
        {{ form.DELETE|add_class:"not_displayed" }}                   # custom filter
        <img src="{{ form.instance.spec_prod.product.picture.url }}">
            {{ form.quantity.label_tag }}
            {{ form.quantity }}
            {{ form.errors }}
            <button type="button">Delete</button>
    {% endfor %}
    <button type="submit">Submit</button>
</form>

Next, if user press 'DELETE' button, my JavaScript
1. hides form with $(item).css('display', 'none');
2. makes checked form.DELETE checkbox with ItemDelCheckbox.prop('checked', true);
3. sends ajax request to delete item from database (otherwise if user refreshes the page, the item still in the cart)

views.py

def delete_order_item(request):            # meets the ajax request
    item_id = int(request.POST['item_id'])
    order = get_object_or_404(Order, pk=int(request.POST['order_id']))
    order.remove_item(item_id)
    if order.is_empty():                   # if the last item is deleted
        order.untie(request.session)
        order.delete()
    return HttpResponse()

Instead of manually creating the hidden field using {{ form.DELETE }}, you can probably use 'can_delete' while instantiating the formset which does the same. For eg,

ArticleFormSet = formset_factory(ArticleForm, can_delete=True)

Refer can_delete

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM