Django form with unknown number of checkbox fields and multiple actions

27,245

Solution 1

Multiple checkboxes with the same name are all the same field.

<input type="checkbox" value="{{item.id}}" name="choices">
<input type="checkbox" value="{{item.id}}" name="choices">
<input type="checkbox" value="{{item.id}}" name="choices">

you can collect and aggregate them with a single django form field.

class UnknownForm(forms.Form):
    choices = forms.MultipleChoiceField(
        choices = LIST_OF_VALID_CHOICES, # this is optional
        widget  = forms.CheckboxSelectMultiple,
    )

Specifically, you can use a ModelMultipleChoiceField.

class UnknownForm(forms.Form):
    choices = forms.ModelMultipleChoiceField(
        queryset = queryset_of_valid_choices, # not optional, use .all() if unsure
        widget  = forms.CheckboxSelectMultiple,
    )

if request.method == 'POST':
    form = UnknownForm(request.POST):
    if 'delete' in request.POST:
        for item in form.cleaned_data['choices']:
            item.delete()
    if 'mark_read' in request.POST:
        for item in form.cleaned_data['choices']:
            item.read = True; item.save()

Solution 2

I can't comment Thomas solution so I do it here.

For ModelMultipleChoiceField the argument name is not choices but queryset.

So to take the last example:

class UnknownForm(forms.Form):
choices = forms.ModelMultipleChoiceField(
    choices = queryset_of_valid_choices, # not optional, use .all() if unsure
    widget  = forms.CheckboxSelectMultiple,
)

Solution 3

I was having this same issue using a class base view and iterating through a list of unknown size in the Django HTML template.

This solution uses 'post' and works for me. I'm putting this here because the above solutions were helpful but didn't solve the looping issue for me.

HTML Template:

<form action="" method="post">
    {% for item in object_list %}
        <input type="checkbox" value="{{item.id}}" name="my_object">
        {{ item.name }}
    {% endfor %}
    <button type="submit" name="delete">Delete</button>
    <button type="submit" name="mark_read">Mark read</button>
</form>

Form:

class MyForm(forms.Form):
    my_object = forms.MultipleChoiceField(
        widget=forms.CheckboxSelectMultiple,
    )

In the class-based view, access the POST data using a post function. This will allow access to a list of your checked items, your context, and other form data.

View:

Class MyView(FormView):
    template_name = "myawesometemplate.html"
    form_class = MyForm
...
# add your code here 

    def post(self, request, *args, **kwargs):
        ...
        context = self.get_context_data()
        ...
        if 'delete' in request.POST:
            for item in form.POST.getlist('my_object'):
                # Delete
        if 'mark_read' in request.POST:
            for item in form.POST.getlist('my_object'):
                # Mark as read
Share:
27,245
Goran
Author by

Goran

Updated on March 13, 2021

Comments

  • Goran
    Goran about 3 years

    I need help with form which looks like a Gmail inbox and have multiple actions. There is a list of items and I want to wrap it with form, on the way that every item have checkbox in the front of the line. So when user select few items he is able to click on two buttons with different actions for example delete and mark read.

    <form action="">
        {% for item in object_list %}
        <input type="checkbox" id="item.id">
        {{ item.name }}
        {% endfor %}
        <button type="submit" name="delete">Delete</button>
        <button type="submit" name="mark_read">Mark read</button>
    </form>
    

    I can find which submit button is user click on if use if 'delete' in request.POST but I cant refer to any form because Django form cant be defined with unknown number of fields as I think. So how can I process selected items in view?

    if request.method == 'POST':
        form = UnknownForm(request.POST):
        if 'delete' in request.POST:
            'delete selected items'
        if 'mark_read' in erquest.POST:
            'mark selected items as read'
        return HttpResponseRedirect('')