显示转义的 Django 自定义小部件 html
Django custom widget displaying escaped html
我为 Django 表单编写了一个小部件,以便在模板中直接拥有一个 bootstrap3 多个复选框,以便将来重用它。
所以我编写了一个包含所有基本文件(__init__.py
、views.py
...)的应用程序 prettyforms
并创建了一个 forms.py 我在其中编写了自定义小部件.
视图item/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')
}
包含widgetprettyforms/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 结果只有 PrettyChoiceFieldRenderer
中的 inner_html
被转义,而 outer_html
没有被转义,这意味着 html 没有被模板系统转义但之前是小部件渲染器。但我不知道在哪里。如果有人能发现不正确的地方,这就是我寻求帮助的地方。
这是'categories'字段的html
如您所见,outer_html
未转义,但 inner_html
是
<p>
<label for="id_categories_0">Categories:</label>
<div class="btn-group" data-toggle="buttons" id="id_categories">
<label class="btn btn-primary"><input id="id_categories_0" name="categories" type="checkbox" value="1" autocomplete="off" /> electronics</label><label class="btn
btn-primary"><input id="id_categories_1" name="categories" type="checkbox" value="2" autocomplete="off" /> bedroom and productivity</label> <label class="btn btn-primary"><input
id="id_categories_2" name="categories" type="checkbox" value="3" autocomplete="off" /> phone</label> <label class="btn btn-primary"><input id="id_categories_3"
name="categories" type="checkbox" value="4" autocomplete="off" /> office</label> <label class="btn btn-primary"><input id="id_categories_4" name="categories"
type="checkbox" value="6" autocomplete="off" /> Kitchen</label>
</div>
</p>
提前致谢
@html_safe 装饰器将 __str__
和 __html__
的输出标记为安全。它不会为您的渲染方法做任何事情。您想要 mark_safe 您的 return 字符串。
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))))
好的,我解决了这个问题。我删除了
中的 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
一定是有原因的
我为 Django 表单编写了一个小部件,以便在模板中直接拥有一个 bootstrap3 多个复选框,以便将来重用它。
所以我编写了一个包含所有基本文件(__init__.py
、views.py
...)的应用程序 prettyforms
并创建了一个 forms.py 我在其中编写了自定义小部件.
视图item/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')
}
包含widgetprettyforms/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 结果只有 PrettyChoiceFieldRenderer
中的 inner_html
被转义,而 outer_html
没有被转义,这意味着 html 没有被模板系统转义但之前是小部件渲染器。但我不知道在哪里。如果有人能发现不正确的地方,这就是我寻求帮助的地方。
这是'categories'字段的html
如您所见,outer_html
未转义,但 inner_html
是
<p>
<label for="id_categories_0">Categories:</label>
<div class="btn-group" data-toggle="buttons" id="id_categories">
<label class="btn btn-primary"><input id="id_categories_0" name="categories" type="checkbox" value="1" autocomplete="off" /> electronics</label><label class="btn
btn-primary"><input id="id_categories_1" name="categories" type="checkbox" value="2" autocomplete="off" /> bedroom and productivity</label> <label class="btn btn-primary"><input
id="id_categories_2" name="categories" type="checkbox" value="3" autocomplete="off" /> phone</label> <label class="btn btn-primary"><input id="id_categories_3"
name="categories" type="checkbox" value="4" autocomplete="off" /> office</label> <label class="btn btn-primary"><input id="id_categories_4" name="categories"
type="checkbox" value="6" autocomplete="off" /> Kitchen</label>
</div>
</p>
提前致谢
@html_safe 装饰器将 __str__
和 __html__
的输出标记为安全。它不会为您的渲染方法做任何事情。您想要 mark_safe 您的 return 字符串。
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))))
好的,我解决了这个问题。我删除了
中的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
一定是有原因的