在Django模板中分组复选框选择多项 [英] Grouped CheckboxSelectMultiple in Django template

查看:121
本文介绍了在Django模板中分组复选框选择多项的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

如何通过相关模型分组由 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)

类Widget(models.Model):
name = models.CharField(max_length = 30)
features = models.ManyToManyField(Feature,blank = True)

forms.py:

 类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}} 

以上产生以下输出:

  []小部件1 
[]小部件2
[]小部件3
[]小部件1
[]小部件2

我想要的功能复选框按功能类别分组(基于 ForeignKey ):

 类别1:
[]小部件1
[]小部件2
[] Widget 3

类别2:
[]小部件1
[]小部件2

我该如何实现?我尝试使用 {%重组%} 模板标签无效。



任何建议都非常感激。 / p>

谢谢。

解决方案

您必须编写自定义 CheckboxSelectMultiple 小部件。使用代码段我已尝试使 CheckboxSelectMultiple 通过将 category_name 作为字段 attrs 中的属性添加到字段中。所以我可以在以后的模板中使用 reroup 标签。



以下代码根据您的需要,显然这个代码可以做得更干净,更通用,但在这一刻它不是通用的。



forms.py


$ d $ d
从django.forms导入Widget
从django.forms。导入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
$ b from mysite.models import Widget as wid#你的模型名称与django.forms.Widget有冲突
from mysite.models import功能

class CheckboxInput(SubWidget):

CheckboxRenderer用于表示单个
<输入的对象类型='checkbo X取代。

def __init __(self,name,value,attrs,choice,index):
self.name,self.value = name,value
self.attrs = attrs
self.choice_value = force_unicode(choice [1])$ ​​b $ b self.choice_label = force_unicode(choice [2])

self.attrs.update({'cat_name'选项[0]})

self.index = index

def __unicode __(self):
return self.render()

def render(self,name = None,value = None,attrs = None,choices =()):
name = name或self.name
value = value或self.value
attrs = attrs或self.attrs

如果self.attrs中的'id':
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_fo r,self.tag(),choice_label))

def is_checked(self):
return self.choice_value in self.value

def tag(self) :
如果self.attrs中的'id':
self.attrs ['id'] ='%s_%s'%(self.attrs ['id'],self.index)
final_attrs = dict(self.attrs,type ='checkbox',name = self.name,value = self.choice_value)
如果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,choice):
self.name,self.value,self.attrs = name,value,attrs
self.choices = choices

def __iter __自我):
为我,在枚举(self.choices)中的选择:
yield CheckboxInput(self.name,self.value,self.attrs.copy(),choice,i)

def __getitem __(self,idx):
choice = self.choices [idx]#让IndexError propogate
返回CheckboxInput(self.name,self.value,self.attrs.copy(),choice,idx)

def __unicode__ (self):
return self.render()

def render(self):
输出< ul>对于这组复选框字段。
return mark_safe(u'< ul> \\\
%s\\\
< / ul>'%u'\\\
'.join([u' ; li>%s< / li>'
%force_unicode(w)for w in self]))

class CheckboxSelectMultipleIter(forms.CheckboxSelectMultiple):

复选框多选择字段,可以启用每个复选框的迭代
类似于django.forms.widgets.RadioSelect

renderer = CheckboxRenderer

def __init__ (self,* args,** kwargs):
#覆盖默认渲染器如果我们被传递一个
renderer = kwargs.pop('renderer',None)
如果渲染器:
self.renderer = renderer
super(CheckboxSelectMultipleIter,self).__ init __(* args,** kwargs)

def subwidgets(self,name,value,attrs = None,choices =()):
for widget在self.get_renderer(name,value,attrs,choices)中:
yield widget

def get_renderer(self,name,value,attrs = None,choices =()):
返回渲染器的一个实例。

choices_ = [对于a,b,c在...中的
choices_ = [(a [1],b [1],c [1])的ast.literal_eval(i [1])。iteritems() values_]

如果值为None:value =''
str_values = set([force_unicode(v)for v in value])#规范化为字符串。
如果attrs是None:
attrs = {}
如果'id'不在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_):
如果id_:
id_ + ='_0'
返回id_

类WidgetForm(forms.ModelForm):
features = forms.ModelMultipleChoiceField(
queryset = Feature.objects.all()。values('id','name','category__name'),
widget = CheckboxSelectMultipleIter,
required = False

class Meta:
model = wid

然后在模板中:

  {%for fi eld in form%} 
{%if field.name =='features'%}
{%regroup field by attrs.cat_name as list%}

< ul>
{%for列表%}
< li> {{el.grouper}}
< ul>
{%for e in el.list%}
{{e}}< br />
{%endfor%}
< / ul>
< / li>
{%endfor%}
< / ul>
{%else%}
{{field.label}}:{{field}}
{%endif%}

{%endfor%}

结果
我在类别表中添加了国家/地区名称,而城市在功能表中的名称,因此在模板中我可以根据国家(类别)重新组合城市(功能)




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

This is best demonstrated by example.

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 }}

The above produces the following output:

[] Widget 1
[] Widget 2
[] Widget 3
[] Widget 1
[] Widget 2

What I would like is for the feature checkboxes to be grouped by feature category (based on the ForeignKey):

Category 1:
  [] Widget 1
  [] Widget 2
  [] Widget 3

Category 2:
  [] Widget 1
  [] Widget 2

How can I achieve this? I have tried using the {% regroup %} template tag to no avail.

Any advice much appreciated.

Thanks.

解决方案

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
    <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

Then in template:

{% 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 %}

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)

这篇关于在Django模板中分组复选框选择多项的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆