puchar/crispy_forms/layout.py

1003 lines
33 KiB
Python
Raw Normal View History

2022-12-05 04:12:09 +01:00
from dataclasses import dataclass
from typing import List
from django.template import Template
from django.template.loader import render_to_string
from django.utils.html import conditional_escape
from django.utils.safestring import SafeString
from django.utils.text import slugify
from crispy_forms.utils import TEMPLATE_PACK, flatatt, render_field
@dataclass
class Pointer:
positions: List[int]
name: str
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 Pointers. First parameter is the location of the
field, second one the name of the field. Example::
[
Pointer([0,1,2], 'field_name1'),
Pointer([0,3], 'field_name2'),
]
"""
return self.get_layout_objects(str, index=None, greedy=True)
def get_layout_objects(self, *LayoutClasses, index=None, max_level=0, greedy=False):
"""
Returns a list of Pointers pointing to layout objects of any type matching
`LayoutClasses`::
[
Pointer([0,1,2], 'div']),
Pointer([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.
"""
pointers = []
if index is not None and not isinstance(index, list):
index = [index]
elif index is None:
index = []
str_class = len(LayoutClasses) == 1 and LayoutClasses[0] == str
for i, layout_object in enumerate(self.fields):
if isinstance(layout_object, LayoutClasses):
if str_class:
pointers.append(Pointer(index + [i], layout_object))
else:
pointers.append(Pointer(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, context, template_pack=TEMPLATE_PACK, **kwargs):
return SafeString(
"".join(render_field(field, form, 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('<img src="/media/somepicture.jpg"/>'),
'company'
),
ButtonHolder(
Submit('Save', 'Save', css_class='button white'),
),
)
"""
def __init__(self, *fields):
self.fields = list(fields)
def render(self, form, context, template_pack=TEMPLATE_PACK, **kwargs):
return self.get_rendered_fields(form, context, template_pack, **kwargs)
class ButtonHolder(LayoutObject):
"""
Layout object. It wraps fields in a <div class="buttonHolder">
This is where you should put Layout objects that render to form buttons
Attributes
----------
template: str
The default template which this Layout Object will be rendered
with.
Parameters
----------
*fields : HTML or BaseInput
The layout objects to render within the ``ButtonHolder``. It should
only hold `HTML` and `BaseInput` inherited objects.
css_id : str, optional
A custom DOM id for the layout object. If not provided the name
argument is slugified and turned into the id for the submit button.
By default None.
css_class : str, optional
Additional CSS classes to be applied to the ``<input>``. By default
None.
template : str, optional
Overrides the default template, if provided. By default None.
Examples
--------
An example using ``ButtonHolder`` in your layout::
ButtonHolder(
HTML(<span style="display: hidden;">Information Saved</span>),
Submit('Save', 'Save')
)
"""
template = "%s/layout/buttonholder.html"
def __init__(self, *fields, css_id=None, css_class=None, template=None):
self.fields = list(fields)
self.css_id = css_id
self.css_class = css_class
self.template = template or self.template
def render(self, form, context, template_pack=TEMPLATE_PACK, **kwargs):
html = self.get_rendered_fields(form, 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.
Attributes
----------
template: str
The default template which this Layout Object will be rendered
with.
field_classes: str
CSS classes to be applied to the ``<input>``.
Parameters
----------
name : str
The name attribute of the button.
value : str
The value attribute of the button.
css_id : str, optional
A custom DOM id for the layout object. If not provided the name
argument is slugified and turned into the id for the submit button.
By default None.
css_class : str, optional
Additional CSS classes to be applied to the ``<input>``. By default
None.
template : str, optional
Overrides the default template, if provided. By default None.
**kwargs : dict, optional
Additional attributes are passed to `flatatt` and converted into
key="value", pairs. These attributes are added to the ``<input>``.
"""
template = "%s/layout/baseinput.html"
field_classes = ""
def __init__(self, name, value, *, css_id=None, css_class=None, template=None, **kwargs):
self.name = name
self.value = value
if css_id:
self.id = css_id
else:
slug = slugify(self.name)
self.id = f"{self.input_type}-id-{slug}"
self.attrs = {}
if css_class:
self.field_classes += f" {css_class}"
self.template = template or self.template
self.flat_attrs = flatatt(kwargs)
def render(self, form, context, template_pack=TEMPLATE_PACK, **kwargs):
"""
Renders an `<input />` 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.
Attributes
----------
template: str
The default template which this Layout Object will be rendered
with.
field_classes: str
CSS classes to be applied to the ``<input>``.
input_type: str
The ``type`` attribute of the ``<input>``.
Parameters
----------
name : str
The name attribute of the button.
value : str
The value attribute of the button.
css_id : str, optional
A custom DOM id for the layout object. If not provided the name
argument is slugified and turned into the id for the submit button.
By default None.
css_class : str, optional
Additional CSS classes to be applied to the ``<input>``. By default
None.
template : str, optional
Overrides the default template, if provided. By default None.
**kwargs : dict, optional
Additional attributes are passed to `flatatt` and converted into
key="value", pairs. These attributes are added to the ``<input>``.
Examples
--------
Note: ``form`` arg to ``render()`` is not required for ``BaseInput``
inherited objects.
>>> submit = Submit('Search the Site', 'search this site')
>>> submit.render("", "", Context())
'<input type="submit" name="search-the-site" value="search this site" '
'class="btn btn-primary" id="submit-id-search-the-site"/>'
>>> submit = Submit('Search the Site', 'search this site', css_id="custom-id",
css_class="custom class", my_attr=True, data="my-data")
>>> submit.render("", "", Context())
'<input type="submit" name="search-the-site" value="search this site" '
'class="btn btn-primary custom class" id="custom-id" data="my-data" my-attr/>'
Usually you will not call the render method on the object directly. Instead
add it to your ``Layout`` manually or use the `add_input` method::
class ExampleForm(forms.Form):
[...]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.add_input(Submit('submit', 'Submit'))
"""
input_type = "submit"
field_classes = "btn btn-primary"
class Button(BaseInput):
"""
Used to create a button descriptor for the {% crispy %} template tag.
Attributes
----------
template: str
The default template which this Layout Object will be rendered
with.
field_classes: str
CSS classes to be applied to the ``<input>``.
input_type: str
The ``type`` attribute of the ``<input>``.
Parameters
----------
name : str
The name attribute of the button.
value : str
The value attribute of the button.
css_id : str, optional
A custom DOM id for the layout object. If not provided the name
argument is slugified and turned into the id for the submit button.
By default None.
css_class : str, optional
Additional CSS classes to be applied to the ``<input>``. By default
None.
template : str, optional
Overrides the default template, if provided. By default None.
**kwargs : dict, optional
Additional attributes are passed to `flatatt` and converted into
key="value", pairs. These attributes are added to the ``<input>``.
Examples
--------
Note: ``form`` arg to ``render()`` is not required for ``BaseInput``
inherited objects.
>>> button = Button('Button 1', 'Press Me!')
>>> button.render("", "", Context())
'<input type="button" name="button-1" value="Press Me!" '
'class="btn" id="button-id-button-1"/>'
>>> button = Button('Button 1', 'Press Me!', css_id="custom-id",
css_class="custom class", my_attr=True, data="my-data")
>>> button.render("", "", Context())
'<input type="button" name="button-1" value="Press Me!" '
'class="btn custom class" id="custom-id" data="my-data" my-attr/>'
Usually you will not call the render method on the object directly. Instead
add it to your ``Layout`` manually or use the `add_input` method::
class ExampleForm(forms.Form):
[...]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.add_input(Button('Button 1', 'Press Me!'))
"""
input_type = "button"
field_classes = "btn"
class Hidden(BaseInput):
"""
Used to create a Hidden input descriptor for the {% crispy %} template tag.
Attributes
----------
template: str
The default template which this Layout Object will be rendered
with.
field_classes: str
CSS classes to be applied to the ``<input>``.
input_type: str
The ``type`` attribute of the ``<input>``.
Parameters
----------
name : str
The name attribute of the button.
value : str
The value attribute of the button.
css_id : str, optional
A custom DOM id for the layout object. If not provided the name
argument is slugified and turned into the id for the submit button.
By default None.
css_class : str, optional
Additional CSS classes to be applied to the ``<input>``. By default
None.
template : str, optional
Overrides the default template, if provided. By default None.
**kwargs : dict, optional
Additional attributes are passed to `flatatt` and converted into
key="value", pairs. These attributes are added to the ``<input>``.
Examples
--------
Note: ``form`` arg to ``render()`` is not required for ``BaseInput``
inherited objects.
>>> hidden = Hidden("hidden", "hide-me")
>>> hidden.render("", "", Context())
'<input type="hidden" name="hidden" value="hide-me"/>'
Usually you will not call the render method on the object directly. Instead
add it to your ``Layout`` manually or use the `add_input` method::
class ExampleForm(forms.Form):
[...]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.add_input(Hidden("hidden", "hide-me"))
"""
input_type = "hidden"
field_classes = "hidden"
class Reset(BaseInput):
"""
Used to create a reset button descriptor for the {% crispy %} template tag.
Attributes
----------
template: str
The default template which this Layout Object will be rendered
with.
field_classes: str
CSS classes to be applied to the ``<input>``.
input_type: str
The ``type`` attribute of the ``<input>``.
Parameters
----------
name : str
The name attribute of the button.
value : str
The value attribute of the button.
css_id : str, optional
A custom DOM id for the layout object. If not provided the name
argument is slugified and turned into the id for the submit button.
By default None.
css_class : str, optional
Additional CSS classes to be applied to the ``<input>``. By default
None.
template : str, optional
Overrides the default template, if provided. By default None.
**kwargs : dict, optional
Additional attributes are passed to `flatatt` and converted into
key="value", pairs. These attributes are added to the ``<input>``.
Examples
--------
Note: ``form`` arg to ``render()`` is not required for ``BaseInput``
inherited objects.
>>> reset = Reset('Reset This Form', 'Revert Me!')
>>> reset.render("", "", Context())
'<input type="reset" name="reset-this-form" value="Revert Me!" '
'class="btn btn-inverse" id="reset-id-reset-this-form"/>'
>>> reset = Reset('Reset This Form', 'Revert Me!', css_id="custom-id",
css_class="custom class", my_attr=True, data="my-data")
>>> reset.render("", "", Context())
'<input type="reset" name="reset-this-form" value="Revert Me!" '
'class="btn btn-inverse custom class" id="custom-id" data="my-data" my-attr/>'
Usually you will not call the render method on the object directly. Instead
add it to your ``Layout`` manually manually or use the `add_input` method::
class ExampleForm(forms.Form):
[...]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.add_input(Reset('Reset This Form', 'Revert Me!'))
"""
input_type = "reset"
field_classes = "btn btn-inverse"
class Fieldset(LayoutObject):
"""
A layout object which wraps fields in a ``<fieldset>``
Parameters
----------
legend : str
The content of the fieldset's ``<legend>``. This text is context
aware, to bring this to life see the examples section.
*fields : str
Any number of fields as positional arguments to be rendered within
the ``<fieldset>``
css_class : str, optional
Additional CSS classes to be applied to the ``<input>``. By default
None.
css_id : str, optional
A custom DOM id for the layout object. If not provided the name
argument is slugified and turned into the id for the submit button.
By default None.
template : str, optional
Overrides the default template, if provided. By default None.
**kwargs : dict, optional
Additional attributes are passed to ``flatatt`` and converted into
key="value", pairs. These attributes are added to the ``<fieldset>``.
Examples
--------
The Fieldset Layout object is added to your ``Layout`` for example::
Fieldset("Text for the legend",
"form_field_1",
"form_field_2",
css_id="my-fieldset-id",
css_class="my-fieldset-class",
data="my-data"
)
The above layout will be rendered as::
'''
<fieldset id="fieldset-id" class="my-fieldset-class" data="my-data">
<legend>Text for the legend</legend>
# form fields render here
</fieldset>
'''
The first parameter is the text for the fieldset legend. This text is context aware,
so you can do things like::
Fieldset("Data for {{ user.username }}",
'form_field_1',
'form_field_2'
)
"""
template = "%s/layout/fieldset.html"
def __init__(self, legend, *fields, css_class=None, css_id=None, template=None, **kwargs):
self.fields = list(fields)
self.legend = legend
self.css_class = css_class
self.css_id = css_id
self.template = template or self.template
self.flat_attrs = flatatt(kwargs)
def render(self, form, context, template_pack=TEMPLATE_PACK, **kwargs):
fields = self.get_rendered_fields(form, context, template_pack, **kwargs)
if self.legend:
legend = Template(str(self.legend)).render(context)
else:
legend = SafeString("")
template = self.get_template_name(template_pack)
return render_to_string(template, {"fieldset": self, "legend": legend, "fields": fields})
class MultiField(LayoutObject):
"""
MultiField container for Bootstrap3. Renders to a MultiField <div>.
Attributes
----------
template: str
The default template which this Layout Object will be rendered
with.
field_template: str
The template which fields will be rendered with.
Parameters
----------
label: str
The label for the multifield.
*fields: str
The fields to be rendered within the multifield.
label_class: str, optional
CSS classes to be added to the multifield label. By default None.
help_text: str, optional
Help text will be available in the context of the multifield template.
This is unused in the bootstrap3 template provided. By default None.
css_class : str, optional
Additional CSS classes to be applied to the ``<input>``. By default
None.
css_id : str, optional
A DOM id for the layout object which will be added to the wrapping
``<div>`` if provided. By default None.
template : str, optional
Overrides the default template, if provided. By default None.
field_template : str, optional
Overrides the default template, if provided. By default None.
**kwargs : dict, optional
Additional attributes are passed to ``flatatt`` and converted into
key="value", pairs. These attributes are added to the wrapping
``<div>``.
"""
template = "%s/layout/multifield.html"
field_template = "%s/multifield.html"
def __init__(
self,
label,
*fields,
label_class=None,
help_text=None,
css_class=None,
css_id=None,
template=None,
field_template=None,
**kwargs,
):
self.fields = list(fields)
self.label_html = label
self.label_class = label_class or "blockLabel"
self.css_class = css_class or "ctrlHolder"
self.css_id = css_id
self.help_text = help_text
self.template = template or self.template
self.field_template = field_template or self.field_template
self.flat_attrs = flatatt(kwargs)
def render(self, form, context, template_pack=TEMPLATE_PACK, **kwargs):
# If a field within MultiField contains errors
if context["form_show_errors"]:
for field in (pointer.name for pointer in self.get_field_names()):
if field in form.errors:
self.css_class += " error"
field_template = self.field_template % template_pack
fields_output = self.get_rendered_fields(
form,
context,
template_pack,
template=field_template,
labelclass=self.label_class,
layout_object=self,
**kwargs,
)
template = self.get_template_name(template_pack)
context.update({"multifield": self, "fields_output": fields_output})
return render_to_string(template, context.flatten())
class Div(LayoutObject):
"""
Layout object. It wraps fields in a ``<div>``.
Attributes
----------
template : str
The default template which this Layout Object will be rendered
with.
css_class : str, optional
CSS classes to be applied to the ``<div>``. By default None.
Parameters
----------
*fields : str, LayoutObject
Any number of fields as positional arguments to be rendered within
the ``<div>``.
css_id : str, optional
A DOM id for the layout object which will be added to the ``<div>`` if
provided. By default None.
css_class : str, optional
Additional CSS classes to be applied in addition to those declared by
the class itself. By default None.
template : str, optional
Overrides the default template, if provided. By default None.
**kwargs : dict, optional
Additional attributes are passed to ``flatatt`` and converted into
key="value", pairs. These attributes are added to the ``<div>``.
Examples
--------
In your ``Layout`` you can::
Div(
'form_field_1',
'form_field_2',
css_id='div-example',
css_class='divs',
)
It is also possible to nest Layout Objects within a Div::
Div(
Div(
Field('form_field', css_class='field-class'),
css_class='div-class',
),
Div('form_field_2', css_class='div-class'),
)
"""
template = "%s/layout/div.html"
css_class = None
def __init__(self, *fields, css_id=None, css_class=None, template=None, **kwargs):
self.fields = list(fields)
if self.css_class and css_class:
self.css_class += f" {css_class}"
elif css_class:
self.css_class = css_class
self.css_id = css_id
self.template = template or self.template
self.flat_attrs = flatatt(kwargs)
def render(self, form, context, template_pack=TEMPLATE_PACK, **kwargs):
fields = self.get_rendered_fields(form, context, template_pack, **kwargs)
template = self.get_template_name(template_pack)
return render_to_string(template, {"div": self, "fields": fields})
class Row(Div):
"""
Layout object. It wraps fields in a ``<div>`` and the template adds the
appropriate class to render the contents in a row. e.g. ``form-row`` when
using the Bootstrap4 template pack.
Attributes
----------
template : str
The default template which this Layout Object will be rendered
with.
css_class : str, optional
CSS classes to be applied to the ``<div>``. By default None.
Parameters
----------
*fields : str, LayoutObject
Any number of fields as positional arguments to be rendered within
the ``<div>``.
css_id : str, optional
A DOM id for the layout object which will be added to the ``<div>`` if
provided. By default None.
css_class : str, optional
Additional CSS classes to be applied in addition to those declared by
the class itself. By default None.
template : str, optional
Overrides the default template, if provided. By default None.
**kwargs : dict, optional
Additional attributes are passed to ``flatatt`` and converted into
key="value", pairs. These attributes are added to the ``<div>``.
Examples
--------
In your ``Layout`` you can::
Row('form_field_1', 'form_field_2', css_id='row-example')
It is also possible to nest Layout Objects within a Row::
Row(
Div(
Field('form_field', css_class='field-class'),
css_class='div-class',
),
Div('form_field_2', css_class='div-class'),
)
"""
template = "%s/layout/row.html"
class Column(Div):
"""
Layout object. It wraps fields in a ``<div>`` and the template adds the
appropriate class to render the contents in a column. e.g. ``col-md`` when
using the Bootstrap4 template pack.
Attributes
----------
template : str
The default template which this Layout Object will be rendered
with.
css_class : str, optional
CSS classes to be applied to the ``<div>``. By default None.
Parameters
----------
*fields : str, LayoutObject
Any number of fields as positional arguments to be rendered within
the ``<div>``.
css_id : str, optional
A DOM id for the layout object which will be added to the ``<div>`` if
provided. By default None.
css_class : str, optional
Additional CSS classes to be applied in addition to those declared by
the class itself. If using the Bootstrap4 template pack the default
``col-md`` is removed if this string contins another ``col-`` class.
By default None.
template : str, optional
Overrides the default template, if provided. By default None.
**kwargs : dict, optional
Additional attributes are passed to ``flatatt`` and converted into
key="value", pairs. These attributes are added to the ``<div>``.
Examples
--------
In your ``Layout`` you can::
Column('form_field_1', 'form_field_2', css_id='col-example')
It is also possible to nest Layout Objects within a Row::
Div(
Column(
Field('form_field', css_class='field-class'),
css_class='col-sm,
),
Column('form_field_2', css_class='col-sm'),
)
"""
template = "%s/layout/column.html"
class HTML:
"""
Layout object. It can contain pure HTML and it has access to the whole
context of the page where the form is being rendered.
Examples::
HTML("{% if saved %}Data saved{% endif %}")
HTML('<input type="hidden" name="{{ step_field }}" value="{{ step0 }}" />')
"""
def __init__(self, html):
self.html = html
def render(self, form, context, template_pack=TEMPLATE_PACK, **kwargs):
return Template(str(self.html)).render(context)
class Field(LayoutObject):
"""
A Layout object, usually containing one field name, where you can add
attributes to it easily.
Attributes
----------
template : str
The default template which this Layout Object will be rendered
with.
attrs : dict
Attributes to be applied to the field. These are converted into html
attributes. e.g. ``data_id: 'test'`` in the attrs dict will become
``data-id='test'`` on the field's ``<input>``.
Parameters
----------
*fields : str
Usually a single field, but can be any number of fields, to be rendered
with the same attributes applied.
css_class : str, optional
CSS classes to be applied to the field. These are added to any classes
included in the ``attrs`` dict. By default ``None``.
wrapper_class: str, optional
CSS classes to be used when rendering the Field. This class is usually
applied to the ``<div>`` which wraps the Field's ``<label>`` and
``<input>`` tags. By default ``None``.
template : str, optional
Overrides the default template, if provided. By default ``None``.
**kwargs : dict, optional
Additional attributes are converted into key="value", pairs. These
attributes are added to the ``<div>``.
Examples
--------
Example::
Field('field_name', style="color: #333;", css_class="whatever", id="field_name")
"""
template = "%s/field.html"
attrs = {}
def __init__(self, *fields, css_class=None, wrapper_class=None, template=None, **kwargs):
self.fields = list(fields)
# Make sure shared state is not edited.
self.attrs = self.attrs.copy()
if css_class:
if "class" in self.attrs:
self.attrs["class"] += f" {css_class}"
else:
self.attrs["class"] = css_class
self.wrapper_class = wrapper_class
self.template = template or self.template
# We use kwargs as HTML attributes, turning data_id='test' into data-id='test'
self.attrs.update({k.replace("_", "-"): conditional_escape(v) for k, v in kwargs.items()})
def render(self, form, context, template_pack=TEMPLATE_PACK, extra_context=None, **kwargs):
if extra_context is None:
extra_context = {}
if self.wrapper_class:
extra_context["wrapper_class"] = self.wrapper_class
template = self.get_template_name(template_pack)
return self.get_rendered_fields(
form,
context,
template_pack,
template=template,
attrs=self.attrs,
extra_context=extra_context,
**kwargs,
)
class MultiWidgetField(Field):
"""
Layout object. For fields with :class:`~django.forms.MultiWidget` as
``widget``, you can pass additional attributes to each widget.
Attributes
----------
template : str
The default template which this Layout Object will be rendered
with.
Parameters
----------
*fields : str
Usually a single field, but can be any number of fields, to be rendered
with the same attributes applied.
attrs : str, optional
Additional attrs to be added to each widget. These are added to any
classes included in the ``attrs`` dict. By default ``None``.
wrapper_class: str, optional
CSS classes to be used when rendering the Field. This class is usually
applied to the ``<div>`` which wraps the Field's ``<label>`` and
``<input>`` tags. By default ``None``.
template : str, optional
Overrides the default template, if provided. By default ``None``.
Examples
--------
Example::
MultiWidgetField(
'multiwidget_field_name',
attrs=(
{'style': 'width: 30px;'},
{'class': 'second_widget_class'}
),
)
"""
def __init__(self, *fields, attrs=None, template=None, wrapper_class=None):
self.fields = list(fields)
self.attrs = attrs or {}
self.template = template or self.template
self.wrapper_class = wrapper_class