Django自定义小部件显示转义HTML
问题描述:
我为Django窗体编写了一个小部件,以便在模板中直接使用bootstrap3多选框,以便将来重用它。Django自定义小部件显示转义HTML
因此,我编写了一个应用程序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渲染,但在“类别”字段逃脱。检查html结果只有inner_html
PrettyChoiceFieldRenderer
转义,而outer_html
不是这意味着html不会被模板系统转义,而是之前的widget呈现器转义。但我不知道在哪里。这就是我寻求帮助的地方,如果有人能发现不正确的东西。
这是 '类别' 字段
的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>
预先感谢您
答
好的,我解决了这个问题。我在
output.append(format_html(
self.inner_html,
choice_value=force_text(w),
sub_widgets=''
))
删除force_text
所以它给
output.append(format_html(
self.inner_html,
choice_value=w,
sub_widgets=''
))
必须有原因的Django已经force_text
在https://github.com/django/django/blob/master/django/forms/widgets.py#L728
答
@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))))
正如我所说,outer_html没有逃脱的理由,只有内容是转义,这里'content = mark_safe('\ n'.join(输出))'。虽然我已经尝试了你的答案,但它返回相同的结果。 – tgdn
我不知道在这个答案的时候Django的版本,但在1.9中,'format_html'本身在其输出上调用'mark_safe' –