在工作发现多对多字段在前台表单中显示为单选框,但是不能成功保存,
找了很多资料也没有,就将Django中自带的Checkboxinput类(复选框,能成功保存)
自定义为Radioinput,并自定义RadioSelectForM2M的Widget来实现多对多字段显示
为单选框形式,并能成功保存,代码如下
#-*- coding:utf-8 -*-
from django.forms.widgets import Widget, force_unicode, mark_safe, \flatatt, SelectMultiple, chain, conditional_escape
'''
将django中的CheckboxInput类拷过来,改成自己的RadioInput和RadioSelectForM2M,
因为RadioSelect作为manytomanyfield的widget保存时会出错。
'''
class RadioInput(Widget):
def __init__(self, attrs=None, check_test=bool):
super(RadioInput, self).__init__(attrs)
# check_test is a callable that takes a value and returns True
# if the checkbox should be checked for that value.
self.check_test = check_test
def render(self, name, value, attrs=None):
final_attrs = self.build_attrs(attrs, type='radio', name=name)
try:
result = self.check_test(value)
except: # Silently catch exceptions
result = False
if result:
final_attrs['checked'] = 'checked'
if value not in ('', True, False, None):
# Only add the 'value' attribute if a value is non-empty.
final_attrs['value'] = force_unicode(value)
return mark_safe(u'<input%s />' % flatatt(final_attrs))
def value_from_datadict(self, data, files, name):
if name not in data:
# A missing value means False because HTML form submission does not
# send results for unselected checkboxes.
return False
value = data.get(name)
# Translate true and false strings to boolean values.
values = {'true': True, 'false': False}
if isinstance(value, basestring):
value = values.get(value.lower(), value)
return value
def _has_changed(self, initial, data):
# Sometimes data or initial could be None or u'' which should be the
# same thing as False.
return bool(initial) != bool(data)
class RadioSelectForM2M(SelectMultiple):
def render(self, name, value, attrs=None, choices=()):
if value is None: value = []
has_id = attrs and 'id' in attrs
final_attrs = self.build_attrs(attrs, name=name)
output = [u'<ul>']
# 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 = ''
rb = RadioInput(final_attrs, check_test=lambda value: value in str_values)
option_value = force_unicode(option_value)
rendered_cb = rb.render(name, option_value)
option_label = conditional_escape(force_unicode(option_label))
output.append(u'<li><label%s>%s %s</label></li>' % (label_for, rendered_cb, option_label))
output.append(u'</ul>')
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)