Grouped CheckboxSelectMultiple in Django template

后端 未结 2 1399
抹茶落季
抹茶落季 2020-11-30 00:43

How can I group checkboxes produced by CheckboxSelectMultiple by a related model?

This is best demonstrated by example.

models.py:

2条回答
  •  一生所求
    2020-11-30 01:21

    You have to write the custom CheckboxSelectMultiple widget. Using the snippet I have tried make the CheckboxSelectMultiple field iterable by adding the category_name as an attribute in field attrs. So that I can use regroup tag in template later on.

    The below code is modified from snippet according to your need, obviously this code can be made more cleaner and more generic, but at this moment its not generic.

    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
        .
        """
        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'%s %s' % (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'' % 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 
      for this set of checkbox fields.""" return mark_safe(u'
        \n%s\n
      ' % u'\n'.join([u'
    • %s
    • ' % 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

    Then in template:

    {% for field in form %}
    {% if field.name == 'features' %} 
        {% regroup field by attrs.cat_name as list %}
    
        
      {% for el in list %}
    • {{el.grouper}}
        {% for e in el.list %} {{e}}
        {% endfor %}
    • {% endfor %}
    {% else %} {{field.label}}: {{field}} {% endif %} {% endfor %}

    Results: I added countries name in category table, and cities name in features table so in template I was able to regroup the cities (features) according to country (category)

    enter image description here

提交回复
热议问题