Django - Render CheckboxSelectMultiple() widget individually in template (manually)
This is my simple solution: render CheckboxSelectMultiple() manually in template
<table>
<thead>
<tr>
<td> </td>
<td>V</td>
<td>S</td>
</tr>
</thead>
{% for pk, choice in form.options.field.widget.choices %}
<tr>
<td><a href="/link/{{ choice }}">{{ choice }}</a></td>
<td>
<label for="id_options_{{ forloop.counter0 }}">
<input {% for m2moption in model.m2moptions.all %}{% if option.pk == pk %}checked="checked"{% endif %}{% endfor %} type="checkbox" id="id_options_{{ forloop.counter0 }}" value="{{ pk }}" name="options" />
</label>
</td>
</tr>
{% endfor %}
</table>
http://dev.yaconiello.com/playground/example/one/
Firstly, I'd restructure your models like so. The way you are currently set up, the option/app checkbox relationship would behave poorly. Each Option would only be able to have a single boolean checked value that it shared with ALL App objects.
models
from django.db import models
from django.utils.translation import ugettext as _
class Option(models.Model):
condition = models.CharField(
verbose_name = _(u'Condition Text'),
max_length = 255,
)
option = models.CharField(
verbose_name = _(u'Option Text'),
max_length = 255,
)
def __unicode__(self):
return self.condition
class App(models.Model):
title = models.CharField(
verbose_name = _(u'App Name'),
max_length = 255
)
slug = models.SlugField(
max_length = 50,
unique = True
)
activated = models.BooleanField(
verbose_name = _(u'Activated'),
default = False,
)
options = models.ManyToManyField(
Option,
through="AppOption"
)
def __unicode__(self):
return self.title
class AppOption(models.Model):
app = models.ForeignKey(
App,
verbose_name = _(u'App'),
)
option = models.ForeignKey(
Option,
verbose_name = _(u'Option'),
)
condition_activated = models.BooleanField(
verbose_name = _(u'Condition Activated'),
default = False,
)
option_activated = models.BooleanField(
verbose_name = _(u'Option Activated'),
default = False,
)
class Meta:
unique_together = (("app", "option"),)
def __unicode__(self):
return "%s %s (%s | %s | %s)" % (self.app, self.option, self.app.activated, self.option_activated, self.condition_activated)
secondly, you should use model formsets and model forms with custom logics inside...
forms
from django.forms.models import modelformset_factory
from django import forms
class AppOptionForm(forms.ModelForm):
class Meta:
model = AppOption
fields = ("app", "option", "condition_activated", "option_activated")
AppOptionFormSet = modelformset_factory(AppOption, form=AppOptionForm)
class AppForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(AppForm, self).__init__(*args, **kwargs)
if self.instance:
self.appoptions_prefix = "appoptions-%s"%self.instance.pk
self.appoptions_formset = AppOptionFormSet(prefix=self.appoptions_prefix,
queryset=AppOption.objects.filter(app=self.instance).order_by('option'))
class Meta:
model = App
fields = ("id", "activated",)
AppFormSet = modelformset_factory(App, form=AppForm)
Ok so what just happened is we created a modelform for AppOption
and then turned it into a modelformset.
THEN, we created a modelform for App
that has an overridden init method that instantiates an AppOption formset for the App
model form's instance.
Lastly, we created a modelformset using the App
modelform.
this is a view that saves all of the apps and appoptions
def one(request):
if request.method == 'POST':
formset = AppFormSet(request.POST, prefix="apps") # do some magic to ALSO apply POST to inner formsets
if formset.is_valid(): # do some magic to ALSO validate inner formsets
for form in formset.forms:
# saved App Instances
form.save()
for innerform in form.appoptions_formset:
# saved AppOption instances
innerform.save()
else:
formset = AppFormSet(prefix="apps")
options = Option.objects.all()
return render(
request,
"playground/example/one.html",
{
'formset' : formset,
'options' : options,
}
)
template
this is a test
<style>
thead td {
width: 50px;
height: 100px;
}
.vertical {
-webkit-transform: rotate(-90deg);
-moz-transform: rotate(-90deg);
-ms-transform: rotate(-90deg);
-o-transform: rotate(-90deg);
filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=3);
}
</style>
<form>
<table>
<thead>
<tr>
<td> </td>
<td><p class="vertical">Activate App</p></td>
{% for option in options %}
<td><p class="vertical">{{ option.condition }}</p></td>
<td><p class="vertical">{{ option.option }}</p></td>
{% endfor %}
</tr>
</thead>
{% for form in formset.forms %}
{% if form.instance.pk %}
<tr>
<td align="center">{{ form.instance.title }}{{ form.id.as_hidden }}</td>
<td align="center">{{ form.activated }}</td>
{% for optionform in form.appoptions_formset.forms %}
{% if optionform.instance.pk %}
<td align="center">
{{ optionform.app.as_hidden }}
{{ optionform.app.as_hidden }}
{{ optionform.condition_activated }}
</td>
<td align="center">{{ optionform.option_activated }}</td>
{% endif %}
{% endfor %}
</tr>
{% endif %}
{% endfor %}
</table>
</form>
For those who came here looking to render CheckBoxMultipleSelect manually but in the standard way (the way Django does, using HTML lists), the following is what I came up with (@below-the-radar's solution helped me achieve it)
<ul id="id_{{field.name}}">
{% for pk, choice in field.field.widget.choices %}
<li>
<label for="id_{{field.name}}_{{ forloop.counter0 }}">
<input id="id_{{field.name}}_{{ forloop.counter0 }}" name="{{field.name}}" type="checkbox" value="{{pk}}" />
{{ choice }}
</label>
</li>
{% endfor %}
</ul>