try:
from itertools import izip
except ImportError:
izip = zip
from django import forms, template
from django.conf import settings
from django.template import Context, loader
from crispy_forms.utils import TEMPLATE_PACK, get_template_pack
register = template.Library()
[docs]@register.filter
def is_checkbox(field):
return isinstance(field.field.widget, forms.CheckboxInput)
[docs]@register.filter
def is_password(field):
return isinstance(field.field.widget, forms.PasswordInput)
[docs]@register.filter
def is_radioselect(field):
return isinstance(field.field.widget, forms.RadioSelect)
[docs]@register.filter
def is_select(field):
return isinstance(field.field.widget, forms.Select)
[docs]@register.filter
def is_checkboxselectmultiple(field):
return isinstance(field.field.widget, forms.CheckboxSelectMultiple)
[docs]@register.filter
def is_file(field):
return isinstance(field.field.widget, forms.FileInput)
[docs]@register.filter
def is_multivalue(field):
return isinstance(field.field.widget, forms.MultiWidget)
[docs]@register.filter
def classes(field):
"""
Returns CSS classes of a field
"""
return field.widget.attrs.get('class', None)
[docs]@register.filter
def css_class(field):
"""
Returns widgets class name in lowercase
"""
return field.field.widget.__class__.__name__.lower()
[docs]def pairwise(iterable):
"""s -> (s0,s1), (s2,s3), (s4, s5), ..."""
a = iter(iterable)
return izip(a, a)
[docs]class CrispyFieldNode(template.Node):
def __init__(self, field, attrs):
self.field = field
self.attrs = attrs
self.html5_required = 'html5_required'
[docs] def render(self, context):
# Nodes are not threadsafe so we must store and look up our instance
# variables in the current rendering context first
if self not in context.render_context:
context.render_context[self] = (
template.Variable(self.field),
self.attrs,
template.Variable(self.html5_required)
)
field, attrs, html5_required = context.render_context[self]
field = field.resolve(context)
try:
html5_required = html5_required.resolve(context)
except template.VariableDoesNotExist:
html5_required = False
# If template pack has been overridden in FormHelper we can pick it from context
template_pack = context.get('template_pack', TEMPLATE_PACK)
# There are special django widgets that wrap actual widgets,
# such as forms.widgets.MultiWidget, admin.widgets.RelatedFieldWidgetWrapper
widgets = getattr(field.field.widget, 'widgets', [getattr(field.field.widget, 'widget', field.field.widget)])
if isinstance(attrs, dict):
attrs = [attrs] * len(widgets)
converters = {
'textinput': 'textinput textInput',
'fileinput': 'fileinput fileUpload',
'passwordinput': 'textinput textInput',
}
converters.update(getattr(settings, 'CRISPY_CLASS_CONVERTERS', {}))
for widget, attr in zip(widgets, attrs):
class_name = widget.__class__.__name__.lower()
class_name = converters.get(class_name, class_name)
css_class = widget.attrs.get('class', '')
if css_class:
if css_class.find(class_name) == -1:
css_class += " %s" % class_name
else:
css_class = class_name
if (
template_pack == 'bootstrap3'
and not is_checkbox(field)
and not is_file(field)
and not is_multivalue(field)
):
css_class += ' form-control'
if field.errors:
css_class += ' form-control-danger'
if (
template_pack == 'bootstrap4'
and not is_checkbox(field)
and not is_file(field)
and not is_multivalue(field)
):
css_class += ' form-control'
if field.errors:
css_class += ' is-invalid'
widget.attrs['class'] = css_class
# HTML5 required attribute
if html5_required and field.field.required and 'required' not in widget.attrs:
if field.field.widget.__class__.__name__ is not 'RadioSelect':
widget.attrs['required'] = 'required'
for attribute_name, attribute in attr.items():
attribute_name = template.Variable(attribute_name).resolve(context)
if attribute_name in widget.attrs:
widget.attrs[attribute_name] += " " + template.Variable(attribute).resolve(context)
else:
widget.attrs[attribute_name] = template.Variable(attribute).resolve(context)
return field
[docs]@register.tag(name="crispy_field")
def crispy_field(parser, token):
"""
{% crispy_field field attrs %}
"""
token = token.split_contents()
field = token.pop(1)
attrs = {}
# We need to pop tag name, or pairwise would fail
token.pop(0)
for attribute_name, value in pairwise(token):
attrs[attribute_name] = value
return CrispyFieldNode(field, attrs)
[docs]@register.simple_tag()
def crispy_addon(field, append="", prepend="", form_show_labels=True):
"""
Renders a form field using bootstrap's prepended or appended text::
{% crispy_addon form.my_field prepend="$" append=".00" %}
You can also just prepend or append like so
{% crispy_addon form.my_field prepend="$" %}
{% crispy_addon form.my_field append=".00" %}
"""
if field:
context = Context({
'field': field,
'form_show_errors': True,
'form_show_labels': form_show_labels,
})
template = loader.get_template('%s/layout/prepended_appended_text.html' % get_template_pack())
context['crispy_prepended_text'] = prepend
context['crispy_appended_text'] = append
if not prepend and not append:
raise TypeError("Expected a prepend and/or append argument")
context = context.flatten()
return template.render(context)