Django自定义小部件显示转义的HTML

时间:2015-05-14 13:20:59

标签: python html django templates escaping

我为Django表单编写了一个小部件,以便在模板中直接使用bootstrap3多个复选框,以便将来重用它。

所以我写了一个应用prettyforms,其中包含所有基本文件(__init__.pyviews.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

提前谢谢

2 个答案:

答案 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))))