You may find that you want to use Django's forms.CheckboxSelectMultiple with a forms.ModelMultipleChoiceField. But, for each choice, you want to show a little table row of attrs for that choice (not just the __unicode__ for that model instance). Maybe you would like some actions for each of the model instances in the queryset of choices for the CheckboxSelectMultiple.
Hmm, how to do this? It seems that there must be an easy way. Below is a not so easy way to perform this presentational task. Does Django provide an easier way?
class DeathStarWidget(forms.widgets.SelectMultiple):
'''like checkboxselectmultiple,
allows user to specify:
fields=(,) and the fields of the model will appear in the table.
actions=[{'name': 'clone', 'url_method': 'clone_url', 'image_url': '/media/clone.png'},{...} ... ]
checkboxes, field1, field2, action1, action2'''
def __init__(self, *a, **kw):
self.fields = kw.pop('fields', []) # list of attrs
# actions is sequence of dicts /w keys 'image_url', 'name', 'url_method'
self.actions = kw.pop('actions', [])
self.rns = kw.pop('related_null_string', 'Null')
super(DeathStarWidget, self).__init__(*a, **kw)
def render(self, name, value, attrs=None, choices=()):
from itertools import chain
from django.forms.widgets import CheckboxInput
from django.utils.encoding import force_unicode
from django.utils.html import conditional_escape
from django.utils.safestring import mark_safe
if value is None: value = []
has_id = attrs and 'id' in attrs
final_attrs = self.build_attrs(attrs, name=name)
output = []
# Normalize to strings
str_values = set([force_unicode(v) for v in value])
for i, (option_value, option_label) in enumerate(chain(self.choices, choices)):
# If an ID attribute was given, add a numeric index as a suffix,
# so that the checkboxes don't all have the same ID attribute.
if has_id:
final_attrs = dict(final_attrs, id='%s_%s' % (attrs['id'], i))
label_for = u' for="%s"' % final_attrs['id']
else:
label_for = ''
cb = CheckboxInput(final_attrs, check_test=lambda value: value in str_values)
option_value = force_unicode(option_value)
rendered_cb = cb.render(name, option_value)
option_label = conditional_escape(force_unicode(option_label))
instance = self.choices.queryset.model.objects.get(id=option_value)
rendered_fields = []
for f in self.fields:
try:
v = getattr(instance, f)
if hasattr(v, 'all'):
v = list(getattr(v, 'all')())
if v:
v = ', '.join([unicode(s) for s in v])
else:
v = self.rns # set to related_null_string
elif isinstance(v, datetime.datetime):
v = v.strftime('%b %d, %Y')
if isinstance(v, bool):
rendered_fields.append(
'<td class="%s"><span class="%s">%s</span></td>' % (
f, str(v).lower(), str(v).lower() )
)
else:
rendered_fields.append('<td class="%s">%s</td>' % (f, v))
except AttributeError:
rendered_fields.append('<td class="%s">None</td>' % f)
for a in self.actions:
try:
if 'image_url' in a:
link = '<a href="%s"><img src="%s" alt="%s"/></a>' % (
getattr(instance, a['url_method'])(),
a['image_url'],
a['name']
)
else:
link = '<a href="%s">%s</a>' % (
getattr(instance, a['url_method'])(),
a['name']
)
rendered_fields.append(
'<td class="%s">%s</td>' % (a['name'], link,)
)
except AttributeError:
rendered_fields.append('<td class="%s">None</td>' % a['name'])
output.append('<tr><td class="checkbox">%s</td>%s</tr>' % (rendered_cb, ''.join(rendered_fields)))
#output.append(u'</table>')
return mark_safe(u'\n'.join(output))
def id_for_label(self, id_):
# See the comment for RadioSelect.id_for_label()
if id_:
id_ += '_0'
return id_
id_for_label = classmethod(id_for_label)
The widget takes kwargs, fields and actions. fields should be an iterable of strings to access the fields/properties with getattr for the instances. actions expects a sequence of dictionaries with keys, 'name', 'url_method' and optionally, 'image_url'.
class BazFoosDeathStar(forms.ModelForm):
"""Select the Foos in style"""
foos = forms.ModelMultipleChoiceField(
queryset = Foo.objects.all(),
widget=DeathStarWidget(
fields=('slug', 'countries', 'start_dt', 'some_bool'),
actions=[
{
'name': 'clone',
'url_method': 'clone_url',
'image_url': 'http://www.pointgphone.com/market/Death.Star.Clock_small.png',
}
]
),
required=False,
)
class Meta:
model = Baz
Then, assuming in this example, Foo objects have a field, slug, a M2M, countries, a DateTimeField, start_dt, a BooleanField, some_bool. Foo instances also have a method, clone_url which returns a url for cloning this Foo.
The output then would look something like this:
<tr>
<td class="checkbox">
<div class="checker" id="uniform-id_foos_0">
<span class="checked"><input checked="checked" type="checkbox" name="foos" value="5" id="id_foos_0" style="opacity: 0; "></span>
</div>
</td>
<td class="slug">some</td>
<td class="countries">Canada, United Kingdom, United States</td>
<td class="start_dt">Jul 26, 2010</td>
<td class="some_bool"><span class="true">true</span></td>
<td class="clone">
<a href="/portal/clone-foo/some/"><img src="http://www.pointgphone.com/market/Death.Star.Clock_small.png" alt="clone"></a>
</td>
</tr>
<tr>
<td class="checkbox">
<div class="checker" id="uniform-id_contracts_1">
<span><input type="checkbox" name="foos" value="27" id="id_foos_1" style="opacity: 0; "></span>
</div>
</td>
<td class="slug">for-rilz</td>
<td class="countries">France, Italy</td>
<td class="start_dt">Jan 05, 2011</td>
<td class="some_bool"><span class="true">true</span></td>
<td class="clone">
<a href="/portal/clone-foo/for-rilz/"><img src="http://www.pointgphone.com/market/Death.Star.Clock_small.png" alt="clone"></a>
</td>
</tr>
You provide the the thead, the tbody wrapping and the table tag as well.