我为Django表单编写了一个小部件,以便在模板中直接使用bootstrap3多个复选框,以便将来重用它。
所以我写了一个应用prettyforms
,其中包含所有基本文件(__init__.py
,views.py
...)并创建了一个forms.py,我在其中编写了自定义小部件。
视图项/ forms.py的主要表单是
from django import forms
from django.utils.translation import ugettext as _
from mysite.item.models import Item, ItemCategory
from mysite.prettyforms import forms as prettyforms
class CreateItemForm(forms.ModelForm):
class Meta:
model = Item
fields = (
'categories',
)
widgets = {
'categories': prettyforms.PrettyCheckboxSelectMultiple(),
}
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request', None)
super(CreateItemForm, self).__init__(*args, **kwargs)
categories = ItemCategory.objects.all()
categories_choices = [ [category.pk, category.name] for category in categories ]
self.fields['categories'].choices = categories_choices
self.fields['categories'].error_messages = {
'required': _('At least one category must be selected')
}
包含小部件prettyforms / forms.py的文件如下:
# -*- coding: utf-8 -*-
from django.forms.widgets import (
ChoiceInput, SelectMultiple, RendererMixin,
CheckboxChoiceInput, ChoiceFieldRenderer
)
from django.utils.encoding import (
force_str, force_text, python_2_unicode_compatible,
)
from django.utils.safestring import mark_safe
from django.forms.utils import flatatt
from django.utils.html import format_html, html_safe
@html_safe
@python_2_unicode_compatible
class PrettyChoiceInput(ChoiceInput):
def __str__(self):
return self.render()
def render(self, name=None, value=None, attrs=None, choices=()):
# NOTE: not sure if we need to compute this as we don't use it
if self.id_for_label:
label_for = format_html(' for="{}"', self.id_for_label)
else:
label_for = ''
attrs = dict(self.attrs, **attrs) if attrs else self.attrs
# TODO: create CSS for btn-checkbox for convention
return format_html(
'<label class="btn btn-primary">{} {}</label>',
self.tag(attrs),
self.choice_label
)
def tag(self, attrs=None):
attrs = attrs or self.attrs
final_attrs = dict(attrs, type=self.input_type, name=self.name, value=self.choice_value)
if self.is_checked():
final_attrs['checked'] = 'checked'
# added autocomplete off
return format_html('<input{} autocomplete="off" />', flatatt(final_attrs))
class PrettyCheckboxChoiceInput(PrettyChoiceInput):
input_type = 'checkbox'
def __init__(self, *args, **kwargs):
super(PrettyCheckboxChoiceInput, self).__init__(*args, **kwargs)
self.value = set(force_text(v) for v in self.value)
def is_checked(self):
return self.choice_value in self.value
@html_safe
@python_2_unicode_compatible
class PrettyChoiceFieldRenderer(ChoiceFieldRenderer):
outer_html = '<div class="btn-group" data-toggle="buttons"{id_attr}>{content}</div>'
inner_html = '{choice_value}{sub_widgets}'
def __getitem__(self, idx):
choice = self.choices[idx] # Let the IndexError propagate
return self.choice_input_class(self.name, self.value, self.attrs.copy(), choice, idx)
def __str__(self):
return self.render()
def render(self):
"""
Outputs a <ul> for this set of choice fields.
If an id was given to the field, it is applied to the <ul> (each
item in the list will get an id of `$id_$i`).
"""
id_ = self.attrs.get('id', None)
output = []
for i, choice in enumerate(self.choices):
choice_value, choice_label = choice
if isinstance(choice_label, (tuple, list)):
attrs_plus = self.attrs.copy()
if id_:
attrs_plus['id'] += '_{}'.format(i)
sub_ul_renderer = ChoiceFieldRenderer(name=self.name,
value=self.value,
attrs=attrs_plus,
choices=choice_label)
sub_ul_renderer.choice_input_class = self.choice_input_class
output.append(format_html(self.inner_html, choice_value=choice_value,
sub_widgets=sub_ul_renderer.render()))
else:
w = self.choice_input_class(self.name, self.value,
self.attrs.copy(), choice, i)
output.append(format_html(self.inner_html,
choice_value=force_text(w), sub_widgets=''))
return format_html(self.outer_html,
id_attr=format_html(' id="{}"', id_) if id_ else '',
content=mark_safe('\n'.join(output)))
class PrettyCheckboxFieldRenderer(PrettyChoiceFieldRenderer):
choice_input_class = PrettyCheckboxChoiceInput
class PrettyCheckboxSelectMultiple(RendererMixin, SelectMultiple):
renderer = PrettyCheckboxFieldRenderer
_empty_value = []
类及其方法中的大多数文本都是从widgets.py
复制并粘贴的在模板中我简单地说
{{ form.as_p }}
问题是所有字段都以html呈现,但'categories'字段是转义的。检查html会导致只有来自inner_html
的{{1}}被转义而PrettyChoiceFieldRenderer
不是,这意味着html不会被模板系统转义,而是被小部件渲染器转义。但我不知道在哪里。如果有人能发现不正确的东西,那就是我在寻求帮助的地方。
这是“类别”字段的HTML
您可以看到outer_html
未转义但outer_html
inner_html
提前谢谢
答案 0 :(得分:2)
好吧,我解决了这个问题。我删除了<{p>中的force_text
output.append(format_html(
self.inner_html,
choice_value=force_text(w),
sub_widgets=''
))
所以它给出了
output.append(format_html(
self.inner_html,
choice_value=w,
sub_widgets=''
))
Django必须有https://github.com/django/django/blob/master/django/forms/widgets.py#L728
中force_text
的原因
答案 1 :(得分:0)
@html_safe装饰器将__str__
和__html__
的输出标记为安全。它不会为你的渲染方法做任何事情。你想mark_safe你的返回字符串。
from django.utils.safestring import mark_safe
return mark_safe(format_html(self.outer_html,
id_attr=format_html(' id="{}"', id_) if id_ else '',
content=mark_safe('\n'.join(output))))