[Fixed]-Django form with unknown number of checkbox fields and multiple actions

36👍

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()
👤Thomas

7👍

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

6👍

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,
)
👤y0no

-1👍

I found this to be very useful when adding groups or permissions to a user.

Make sure that you have the default django groups and permissions included in your view for the page.

from django.contrib.auth.models import Permission, Group

If you are pulling the choices from a database table you can use a django form object with a widget to dynamically load all of the available choices. You also need to make sure that your form names are the same as the model name.

    groups = forms.ModelMultipleChoiceField(label='Groups', required=False, queryset=Group.objects.all(), widget=forms.CheckboxSelectMultiple)
user_permissions = forms.ModelMultipleChoiceField(label='Permissions', required=False, queryset=Permission.objects.all(), widget=forms.CheckboxSelectMultiple)

Then in the post section of the view method for that page you can get the selected choices returned as a list of the choice objects, and add them to the user object with a for loop.

u.save()  # required to save twice, so this saves the other form fields.
        user.groups.clear()
        u.user_permissions.clear()
        # print('Group name:', form.cleaned_data['groups'])
        for group in form.cleaned_data['groups']:
            print(group)  # Prints to your console for debugging
            user.groups.add(group)
        for permission in form.cleaned_data['user_permissions']:
            print(permission)  # Prints to your console for debugging
            user.user_permissions.add(permission)
        u.save()  #This saves the groups and permissions

This may still need some logic if no groups are selected, but should be enough to get started.

Leave a comment