You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
190 lines
6.8 KiB
190 lines
6.8 KiB
import logging
|
|
import sys
|
|
from functools import lru_cache
|
|
|
|
from django.conf import settings
|
|
from django.forms.utils import flatatt as _flatatt
|
|
from django.template import Context
|
|
from django.template.loader import get_template
|
|
from django.utils.functional import SimpleLazyObject
|
|
|
|
from .base import KeepContext
|
|
|
|
|
|
def get_template_pack():
|
|
return getattr(settings, "CRISPY_TEMPLATE_PACK", "bootstrap")
|
|
|
|
|
|
TEMPLATE_PACK = SimpleLazyObject(get_template_pack)
|
|
|
|
|
|
# By caching we avoid loading the template every time render_field
|
|
# is called without a template
|
|
@lru_cache()
|
|
def default_field_template(template_pack=TEMPLATE_PACK):
|
|
return get_template("%s/field.html" % template_pack)
|
|
|
|
|
|
def render_field( # noqa: C901
|
|
field,
|
|
form,
|
|
form_style,
|
|
context,
|
|
template=None,
|
|
labelclass=None,
|
|
layout_object=None,
|
|
attrs=None,
|
|
template_pack=TEMPLATE_PACK,
|
|
extra_context=None,
|
|
**kwargs,
|
|
):
|
|
"""
|
|
Renders a django-crispy-forms field
|
|
|
|
:param field: Can be a string or a Layout object like `Row`. If it's a layout
|
|
object, we call its render method, otherwise we instantiate a BoundField
|
|
and render it using default template 'CRISPY_TEMPLATE_PACK/field.html'
|
|
The field is added to a list that the form holds called `rendered_fields`
|
|
to avoid double rendering fields.
|
|
:param form: The form/formset to which that field belongs to.
|
|
:param form_style: A way to pass style name to the CSS framework used.
|
|
:template: Template used for rendering the field.
|
|
:layout_object: If passed, it points to the Layout object that is being rendered.
|
|
We use it to store its bound fields in a list called `layout_object.bound_fields`
|
|
:attrs: Attributes for the field's widget
|
|
:template_pack: Name of the template pack to be used for rendering `field`
|
|
:extra_context: Dictionary to be added to context, added variables by the layout object
|
|
"""
|
|
added_keys = [] if extra_context is None else extra_context.keys()
|
|
with KeepContext(context, added_keys):
|
|
if field is None:
|
|
return ""
|
|
|
|
FAIL_SILENTLY = getattr(settings, "CRISPY_FAIL_SILENTLY", True)
|
|
|
|
if hasattr(field, "render"):
|
|
return field.render(form, form_style, context, template_pack=template_pack)
|
|
|
|
try:
|
|
# Injecting HTML attributes into field's widget, Django handles rendering these
|
|
bound_field = form[field]
|
|
field_instance = bound_field.field
|
|
if attrs is not None:
|
|
widgets = getattr(field_instance.widget, "widgets", [field_instance.widget])
|
|
|
|
# We use attrs as a dictionary later, so here we make a copy
|
|
list_attrs = attrs
|
|
if isinstance(attrs, dict):
|
|
list_attrs = [attrs] * len(widgets)
|
|
|
|
for index, (widget, attr) in enumerate(zip(widgets, list_attrs)):
|
|
if hasattr(field_instance.widget, "widgets"):
|
|
if "type" in attr and attr["type"] == "hidden":
|
|
field_instance.widget.widgets[index] = field_instance.hidden_widget(attr)
|
|
|
|
else:
|
|
field_instance.widget.widgets[index].attrs.update(attr)
|
|
else:
|
|
if "type" in attr and attr["type"] == "hidden":
|
|
field_instance.widget = field_instance.hidden_widget(attr)
|
|
|
|
else:
|
|
field_instance.widget.attrs.update(attr)
|
|
|
|
except KeyError:
|
|
if not FAIL_SILENTLY:
|
|
raise Exception("Could not resolve form field '%s'." % field)
|
|
else:
|
|
field_instance = None
|
|
logging.warning("Could not resolve form field '%s'." % field, exc_info=sys.exc_info())
|
|
|
|
if hasattr(form, "rendered_fields"):
|
|
if field not in form.rendered_fields:
|
|
form.rendered_fields.add(field)
|
|
else:
|
|
if not FAIL_SILENTLY:
|
|
raise Exception("A field should only be rendered once: %s" % field)
|
|
else:
|
|
logging.warning("A field should only be rendered once: %s" % field, exc_info=sys.exc_info())
|
|
|
|
if field_instance is None:
|
|
html = ""
|
|
else:
|
|
if template is None:
|
|
if form.crispy_field_template is None:
|
|
template = default_field_template(template_pack)
|
|
else: # FormHelper.field_template set
|
|
template = get_template(form.crispy_field_template)
|
|
else:
|
|
template = get_template(template)
|
|
|
|
# We save the Layout object's bound fields in the layout object's `bound_fields` list
|
|
if layout_object is not None:
|
|
if hasattr(layout_object, "bound_fields") and isinstance(layout_object.bound_fields, list):
|
|
layout_object.bound_fields.append(bound_field)
|
|
else:
|
|
layout_object.bound_fields = [bound_field]
|
|
|
|
context.update(
|
|
{
|
|
"field": bound_field,
|
|
"labelclass": labelclass,
|
|
"flat_attrs": flatatt(attrs if isinstance(attrs, dict) else {}),
|
|
}
|
|
)
|
|
if extra_context is not None:
|
|
context.update(extra_context)
|
|
|
|
html = template.render(context.flatten())
|
|
|
|
return html
|
|
|
|
|
|
def flatatt(attrs):
|
|
"""
|
|
Convert a dictionary of attributes to a single string.
|
|
|
|
Passed attributes are redirected to `django.forms.utils.flatatt()`
|
|
with replaced "_" (underscores) by "-" (dashes) in their names.
|
|
"""
|
|
return _flatatt({k.replace("_", "-"): v for k, v in attrs.items()})
|
|
|
|
|
|
def render_crispy_form(form, helper=None, context=None):
|
|
"""
|
|
Renders a form and returns its HTML output.
|
|
|
|
This function wraps the template logic in a function easy to use in a Django view.
|
|
"""
|
|
from crispy_forms.templatetags.crispy_forms_tags import CrispyFormNode
|
|
|
|
if helper is not None:
|
|
node = CrispyFormNode("form", "helper")
|
|
else:
|
|
node = CrispyFormNode("form", None)
|
|
|
|
node_context = Context(context)
|
|
node_context.update({"form": form, "helper": helper})
|
|
|
|
return node.render(node_context)
|
|
|
|
|
|
def list_intersection(list1, list2):
|
|
"""
|
|
Take the not-in-place intersection of two lists, similar to sets but preserving order.
|
|
Does not check unicity of list1.
|
|
"""
|
|
return [item for item in list1 if item in list2]
|
|
|
|
|
|
def list_difference(left, right):
|
|
"""
|
|
Take the not-in-place difference of two lists (left - right), similar to sets but preserving order.
|
|
"""
|
|
blocked = set(right)
|
|
difference = []
|
|
for item in left:
|
|
if item not in blocked:
|
|
blocked.add(item)
|
|
difference.append(item)
|
|
return difference
|
|
|