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
