from django.template import Template
from django.template.loader import render_to_string
from django.utils.html import conditional_escape
from crispy_forms.utils import TEMPLATE_PACK, flatatt, get_template_pack, render_field
class TemplateNameMixin:
def get_template_name(self, template_pack):
if "%s" in self.template:
template = self.template % template_pack
else:
template = self.template
return template
class LayoutObject(TemplateNameMixin):
def __getitem__(self, slice):
return self.fields[slice]
def __setitem__(self, slice, value):
self.fields[slice] = value
def __delitem__(self, slice):
del self.fields[slice]
def __len__(self):
return len(self.fields)
def __getattr__(self, name):
"""
This allows us to access self.fields list methods like append or insert, without
having to declare them one by one
"""
# Check necessary for unpickling, see #107
if "fields" in self.__dict__ and hasattr(self.fields, name):
return getattr(self.fields, name)
else:
return object.__getattribute__(self, name)
def get_field_names(self, index=None):
"""
Returns a list of lists, those lists are named pointers. First parameter
is the location of the field, second one the name of the field. Example::
[
[[0,1,2], 'field_name1'],
[[0,3], 'field_name2']
]
"""
return self.get_layout_objects(str, index=None, greedy=True)
def get_layout_objects(self, *LayoutClasses, **kwargs):
"""
Returns a list of lists pointing to layout objects of any type matching
`LayoutClasses`::
[
[[0,1,2], 'div'],
[[0,3], 'field_name']
]
:param max_level: An integer that indicates max level depth to reach when
traversing a layout.
:param greedy: Boolean that indicates whether to be greedy. If set, max_level
is skipped.
"""
index = kwargs.pop("index", None)
max_level = kwargs.pop("max_level", 0)
greedy = kwargs.pop("greedy", False)
pointers = []
if index is not None and not isinstance(index, list):
index = [index]
elif index is None:
index = []
for i, layout_object in enumerate(self.fields):
if isinstance(layout_object, LayoutClasses):
if len(LayoutClasses) == 1 and LayoutClasses[0] == str:
pointers.append([index + [i], layout_object])
else:
pointers.append([index + [i], layout_object.__class__.__name__.lower()])
# If it's a layout object and we haven't reached the max depth limit or greedy
# we recursive call
if hasattr(layout_object, "get_field_names") and (len(index) < max_level or greedy):
new_kwargs = {"index": index + [i], "max_level": max_level, "greedy": greedy}
pointers = pointers + layout_object.get_layout_objects(*LayoutClasses, **new_kwargs)
return pointers
def get_rendered_fields(self, form, form_style, context, template_pack=TEMPLATE_PACK, **kwargs):
return "".join(
render_field(field, form, form_style, context, template_pack=template_pack, **kwargs)
for field in self.fields
)
class Layout(LayoutObject):
"""
Form Layout. It is conformed by Layout objects: `Fieldset`, `Row`, `Column`, `MultiField`,
`HTML`, `ButtonHolder`, `Button`, `Hidden`, `Reset`, `Submit` and fields. Form fields
have to be strings.
Layout objects `Fieldset`, `Row`, `Column`, `MultiField` and `ButtonHolder` can hold other
Layout objects within. Though `ButtonHolder` should only hold `HTML` and BaseInput
inherited classes: `Button`, `Hidden`, `Reset` and `Submit`.
Example::
helper.layout = Layout(
Fieldset('Company data',
'is_company'
),
Fieldset(_('Contact details'),
'email',
Row('password1', 'password2'),
'first_name',
'last_name',
HTML(''),
'company'
),
ButtonHolder(
Submit('Save', 'Save', css_class='button white'),
),
)
"""
def __init__(self, *fields):
self.fields = list(fields)
def render(self, form, form_style, context, template_pack=TEMPLATE_PACK, **kwargs):
return self.get_rendered_fields(form, form_style, context, template_pack, **kwargs)
class ButtonHolder(LayoutObject):
"""
Layout object. It wraps fields in a
This is where you should put Layout objects that render to form buttons like Submit.
It should only hold `HTML` and `BaseInput` inherited objects.
Example::
ButtonHolder(
HTML(Information Saved),
Submit('Save', 'Save')
)
"""
template = "%s/layout/buttonholder.html"
def __init__(self, *fields, **kwargs):
self.fields = list(fields)
self.css_class = kwargs.get("css_class", None)
self.css_id = kwargs.get("css_id", None)
self.template = kwargs.get("template", self.template)
def render(self, form, form_style, context, template_pack=TEMPLATE_PACK, **kwargs):
html = self.get_rendered_fields(form, form_style, context, template_pack, **kwargs)
template = self.get_template_name(template_pack)
context.update({"buttonholder": self, "fields_output": html})
return render_to_string(template, context.flatten())
class BaseInput(TemplateNameMixin):
"""
A base class to reduce the amount of code in the Input classes.
"""
template = "%s/layout/baseinput.html"
def __init__(self, name, value, **kwargs):
self.name = name
self.value = value
self.id = kwargs.pop("css_id", "")
self.attrs = {}
if "css_class" in kwargs:
self.field_classes += " %s" % kwargs.pop("css_class")
self.template = kwargs.pop("template", self.template)
self.flat_attrs = flatatt(kwargs)
def render(self, form, form_style, context, template_pack=TEMPLATE_PACK, **kwargs):
"""
Renders an `` if container is used as a Layout object.
Input button value can be a variable in context.
"""
self.value = Template(str(self.value)).render(context)
template = self.get_template_name(template_pack)
context.update({"input": self})
return render_to_string(template, context.flatten())
class Submit(BaseInput):
"""
Used to create a Submit button descriptor for the {% crispy %} template tag::
submit = Submit('Search the Site', 'search this site')
.. note:: The first argument is also slugified and turned into the id for the submit button.
"""
input_type = "submit"
def __init__(self, *args, **kwargs):
self.field_classes = "submit submitButton" if get_template_pack() == "uni_form" else "btn btn-primary"
super().__init__(*args, **kwargs)
class Button(BaseInput):
"""
Used to create a Submit input descriptor for the {% crispy %} template tag::
button = Button('Button 1', 'Press Me!')
.. note:: The first argument is also slugified and turned into the id for the button.
"""
input_type = "button"
def __init__(self, *args, **kwargs):
self.field_classes = "button" if get_template_pack() == "uni_form" else "btn"
super().__init__(*args, **kwargs)
class Hidden(BaseInput):
"""
Used to create a Hidden input descriptor for the {% crispy %} template tag.
"""
input_type = "hidden"
field_classes = "hidden"
class Reset(BaseInput):
"""
Used to create a Reset button input descriptor for the {% crispy %} template tag::
reset = Reset('Reset This Form', 'Revert Me!')
.. note:: The first argument is also slugified and turned into the id for the reset.
"""
input_type = "reset"
def __init__(self, *args, **kwargs):
self.field_classes = "reset resetButton" if get_template_pack() == "uni_form" else "btn btn-inverse"
super().__init__(*args, **kwargs)
class Fieldset(LayoutObject):
"""
Layout object. It wraps fields in a