如何CheckboxSelectMultiple对相关模型产生的复选框进行分组?
这是最好的例子。
models.py:
class FeatureCategory(models.Model): name = models.CharField(max_length=30) class Feature(models.Model): name = models.CharField(max_length=30) category = models.ForeignKey(FeatureCategory) class Widget(models.Model): name = models.CharField(max_length=30) features = models.ManyToManyField(Feature, blank=True)
forms.py:
class WidgetForm(forms.ModelForm): features = forms.ModelMultipleChoiceField( queryset=Feature.objects.all(), widget=forms.CheckboxSelectMultiple, required=False ) class Meta: model = Widget
views.py:
def edit_widget(request): form = WidgetForm() return render(request, 'template.html', {'form': form}) template.html: {{ form.as_p }}
上面产生了以下输出:
[] Widget 1 [] Widget 2 [] Widget 3 [] Widget 1 [] Widget 2
我想要按功能类别(基于ForeignKey)对功能复选框进行分组:
Category 1: [] Widget 1 [] Widget 2 [] Widget 3 Category 2: [] Widget 1 [] Widget 2
我该如何实现?我尝试使用{% regroup %}模板标签无济于事。
{% regroup %}
任何建议,不胜感激。
你必须编写自定义CheckboxSelectMultiple窗口小部件。通过使用代码段,我尝试通过将CheckboxSelectMultiplefield category_name作为属性添加到字段中以使字段可迭代attrs。这样我以后可以regroup在模板中使用标记。
CheckboxSelectMultiple
CheckboxSelectMultiplefield category_name
attrs
regroup
下面的代码根据你的需要从代码段中进行了修改,显然可以使此代码更简洁,更通用,但是目前还不是通用的。
forms.py
from django import forms from django.forms import Widget from django.forms.widgets import SubWidget from django.forms.util import flatatt from django.utils.html import conditional_escape from django.utils.encoding import StrAndUnicode, force_unicode from django.utils.safestring import mark_safe from itertools import chain import ast from mysite.models import Widget as wid # your model name is conflicted with django.forms.Widget from mysite.models import Feature class CheckboxInput(SubWidget): """ An object used by CheckboxRenderer that represents a single <input type='checkbox'>. """ def __init__(self, name, value, attrs, choice, index): self.name, self.value = name, value self.attrs = attrs self.choice_value = force_unicode(choice[1]) self.choice_label = force_unicode(choice[2]) self.attrs.update({'cat_name': choice[0]}) self.index = index def __unicode__(self): return self.render() def render(self, name=None, value=None, attrs=None, choices=()): name = name or self.name value = value or self.value attrs = attrs or self.attrs if 'id' in self.attrs: label_for = ' for="%s_%s"' % (self.attrs['id'], self.index) else: label_for = '' choice_label = conditional_escape(force_unicode(self.choice_label)) return mark_safe(u'<label%s>%s %s</label>' % (label_for, self.tag(), choice_label)) def is_checked(self): return self.choice_value in self.value def tag(self): if 'id' in self.attrs: self.attrs['id'] = '%s_%s' % (self.attrs['id'], self.index) final_attrs = dict(self.attrs, type='checkbox', name=self.name, value=self.choice_value) if self.is_checked(): final_attrs['checked'] = 'checked' return mark_safe(u'<input%s />' % flatatt(final_attrs)) class CheckboxRenderer(StrAndUnicode): def __init__(self, name, value, attrs, choices): self.name, self.value, self.attrs = name, value, attrs self.choices = choices def __iter__(self): for i, choice in enumerate(self.choices): yield CheckboxInput(self.name, self.value, self.attrs.copy(), choice, i) def __getitem__(self, idx): choice = self.choices[idx] # Let the IndexError propogate return CheckboxInput(self.name, self.value, self.attrs.copy(), choice, idx) def __unicode__(self): return self.render() def render(self): """Outputs a <ul> for this set of checkbox fields.""" return mark_safe(u'<ul>\n%s\n</ul>' % u'\n'.join([u'<li>%s</li>' % force_unicode(w) for w in self])) class CheckboxSelectMultipleIter(forms.CheckboxSelectMultiple): """ Checkbox multi select field that enables iteration of each checkbox Similar to django.forms.widgets.RadioSelect """ renderer = CheckboxRenderer def __init__(self, *args, **kwargs): # Override the default renderer if we were passed one. renderer = kwargs.pop('renderer', None) if renderer: self.renderer = renderer super(CheckboxSelectMultipleIter, self).__init__(*args, **kwargs) def subwidgets(self, name, value, attrs=None, choices=()): for widget in self.get_renderer(name, value, attrs, choices): yield widget def get_renderer(self, name, value, attrs=None, choices=()): """Returns an instance of the renderer.""" choices_ = [ast.literal_eval(i[1]).iteritems() for i in self.choices] choices_ = [(a[1], b[1], c[1]) for a, b, c in choices_] if value is None: value = '' str_values = set([force_unicode(v) for v in value]) # Normalize to string. if attrs is None: attrs = {} if 'id' not in attrs: attrs['id'] = name final_attrs = self.build_attrs(attrs) choices = list(chain(choices_, choices)) return self.renderer(name, str_values, final_attrs, choices) def render(self, name, value, attrs=None, choices=()): return self.get_renderer(name, value, attrs, choices).render() def id_for_label(self, id_): if id_: id_ += '_0' return id_ class WidgetForm(forms.ModelForm): features = forms.ModelMultipleChoiceField( queryset=Feature.objects.all().values('id', 'name', 'category__name'), widget=CheckboxSelectMultipleIter, required=False ) class Meta: model = wid
然后在模板中:
{% for field in form %} {% if field.name == 'features' %} {% regroup field by attrs.cat_name as list %} <ul> {% for el in list %} <li>{{el.grouper}} <ul> {% for e in el.list %} {{e}} <br /> {% endfor %} </ul> </li> {% endfor %} </ul> {% else %} {{field.label}}: {{field}} {% endif %} {% endfor %}
结果:我在类别表中添加了国家名称,在功能表中添加了城市名称,因此在模板中,我能够根据国家(类别)对城市(功能)进行重新分组