python - Django custom widget displaying escaped html -
i wrote widget django forms in order have bootstrap3 multiple checkbox directly in template in order reuse in future.
so wrote app prettyforms contains basic files (__init__.py, views.py ...) , created forms.py wrote custom widget.
the main form view item/forms.py such as
from django import forms django.utils.translation import ugettext _ mysite.item.models import item, itemcategory mysite.prettyforms import forms 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] category in categories ] self.fields['categories'].choices = categories_choices self.fields['categories'].error_messages = { 'required': _('at least 1 category must selected') } and file containing widget prettyforms/forms.py such as:
# -*- coding: utf-8 -*- django.forms.widgets import ( choiceinput, selectmultiple, renderermixin, checkboxchoiceinput, choicefieldrenderer ) django.utils.encoding import ( force_str, force_text, python_2_unicode_compatible, ) django.utils.safestring import mark_safe django.forms.utils import flatatt 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 need compute don't use 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 btn-checkbox 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) 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 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 <ul> set of choice fields. if id given field, applied <ul> (each item in list id of `$id_$i`). """ id_ = self.attrs.get('id', none) output = [] 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 = [] most of text in classes , methods copied , pasted widgets.py
in templates put
{{ form.as_p }} and problem fields rendered in html 'categories' field escaped. inspecting html results inner_html prettychoicefieldrenderer escaped while outer_html not means html not escaped template system widget renderer before. don't know where. that's asking help, if can spot incorrect.
this html of 'categories' field
as can see outer_html not escaped inner_html is
<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 , 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> thank in advance
alright, fixed issue. removed force_text in
output.append(format_html( self.inner_html, choice_value=force_text(w), sub_widgets='' )) so gives
output.append(format_html( self.inner_html, choice_value=w, sub_widgets='' )) there must reason why django has force_text in https://github.com/django/django/blob/master/django/forms/widgets.py#l728
Comments
Post a Comment