2015-05-14 90 views
0

我为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_htmlPrettyChoiceFieldRenderer转义,而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"> 

     &lt;label class=&quot;btn btn-primary&quot;&gt;&lt;input id=&quot;id_categories_0&quot; name=&quot;categories&quot; type=&quot;checkbox&quot; value=&quot;1&quot; autocomplete=&quot;off&quot; /&gt; electronics&lt;/label&gt;&lt;label class=&quot;btn 
     btn-primary&quot;&gt;&lt;input id=&quot;id_categories_1&quot; name=&quot;categories&quot; type=&quot;checkbox&quot; value=&quot;2&quot; autocomplete=&quot;off&quot; /&gt; bedroom and productivity&lt;/label&gt; &lt;label class=&quot;btn btn-primary&quot;&gt;&lt;input 
     id=&quot;id_categories_2&quot; name=&quot;categories&quot; type=&quot;checkbox&quot; value=&quot;3&quot; autocomplete=&quot;off&quot; /&gt; phone&lt;/label&gt; &lt;label class=&quot;btn btn-primary&quot;&gt;&lt;input id=&quot;id_categories_3&quot; 
     name=&quot;categories&quot; type=&quot;checkbox&quot; value=&quot;4&quot; autocomplete=&quot;off&quot; /&gt; office&lt;/label&gt; &lt;label class=&quot;btn btn-primary&quot;&gt;&lt;input id=&quot;id_categories_4&quot; name=&quot;categories&quot; 
     type=&quot;checkbox&quot; value=&quot;6&quot; autocomplete=&quot;off&quot; /&gt; Kitchen&lt;/label&gt; 

    </div> 
</p> 

预先感谢您

回答

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)))) 
+0

正如我所说,outer_html没有逃脱的理由,只有内容是转义,这里'content = mark_safe('\ n'.join(输出))'。虽然我已经尝试了你的答案,但它返回相同的结果。 – tgdn

+0

我不知道在这个答案的时候Django的版本,但在1.9中,'format_html'本身在其输出上调用'mark_safe' –