class FormHelper(DynamicLayoutHandler):
    """
    This class controls the form rendering behavior of the form passed to
    the `{% crispy %}` tag. For doing so you will need to set its attributes
    and pass the corresponding helper object to the tag::

        {% crispy form form.helper %}

    Let's see what attributes you can set and what form behaviors they apply to:

        **form_method**: Specifies form method attribute.
            You can set it to 'POST' or 'GET'. Defaults to 'POST'

        **form_action**: Applied to the form action attribute:
            - Can be a named url in your URLconf that can be executed via the `{% url %}` template tag. \
            Example: 'show_my_profile'. In your URLconf you could have something like::

                path('show/profile/', 'show_my_profile_view', name = 'show_my_profile')

            - It can simply point to a URL '/whatever/blabla/'.

        **form_id**: Generates a form id for dom identification.
            If no id provided then no id attribute is created on the form.

        **form_class**: String containing separated CSS classes to be applied
            to form class attribute.

        **form_group_wrapper_class**: String containing separated CSS classes to be applied
            to each row of inputs.

        **form_tag**: It specifies if <form></form> tags should be rendered when using a Layout.
            If set to False it renders the form without the <form></form> tags. Defaults to True.

        **form_error_title**: If a form has `non_field_errors` to display, they
            are rendered in a div. You can set title's div with this attribute.
            Example: "Oooops!" or "Form Errors"

        **formset_error_title**: If a formset has `non_form_errors` to display, they
            are rendered in a div. You can set title's div with this attribute.

        **include_media**: Whether to automatically include form media. Set to False if
            you want to manually include form media outside the form. Defaults to True.

    Public Methods:

        **add_input(input)**: You can add input buttons using this method. Inputs
            added using this method will be rendered at the end of the form/formset.

        **add_layout(layout)**: You can add a `Layout` object to `FormHelper`. The Layout
            specifies in a simple, clean and DRY way how the form fields should be rendered.
            You can wrap fields, order them, customize pretty much anything in the form.

    Best way to add a helper to a form is adding a property named helper to the form
    that returns customized `FormHelper` object::

        from crispy_forms.helper import FormHelper
        from crispy_forms.layout import Submit

        class MyForm(forms.Form):
            title = forms.CharField(_("Title"))

            @property
            def helper(self):
                helper = FormHelper()
                helper.form_id = 'this-form-rocks'
                helper.form_class = 'search'
                helper.add_input(Submit('save', 'save'))
                [...]
                return helper

    You can use it in a template doing::

        {% load crispy_forms_tags %}
        {% crispy form %}
    """

    _form_method = "post"
    _form_action = ""

    form = None
    form_id = ""
    form_class = ""
    form_group_wrapper_class = ""
    layout = None
    form_tag = True
    form_error_title = None
    formset_error_title = None
    form_show_errors = True
    render_unmentioned_fields = False
    render_hidden_fields = False
    render_required_fields = False
    _help_text_inline = False
    _error_text_inline = True
    form_show_labels = True
    template = None
    field_template = None
    disable_csrf = False
    use_custom_control = True
    label_class = ""
    field_class = ""
    include_media = True

    def __init__(self, form=None):
        self.attrs = {}
        self.inputs = []

        if form is not None:
            self.form = form
            self.layout = self.build_default_layout(form)

    def build_default_layout(self, form):
        return Layout(*form.fields.keys())

    @property
    def form_method(self):
        return self._form_method

    @form_method.setter
    def form_method(self, method):
        if method.lower() not in ("get", "post"):
            raise FormHelpersException(
                "Only GET and POST are valid in the \
                    form_method helper attribute"
            )

        self._form_method = method.lower()

    @property
    def form_action(self):
        try:
            return reverse(self._form_action)
        except NoReverseMatch:
            return self._form_action

    @form_action.setter
    def form_action(self, action):
        self._form_action = action

    @property
    def help_text_inline(self):
        return self._help_text_inline

    @help_text_inline.setter
    def help_text_inline(self, flag):
        self._help_text_inline = flag
        self._error_text_inline = not flag

    @property
    def error_text_inline(self):
        return self._error_text_inline

    @error_text_inline.setter
    def error_text_inline(self, flag):
        self._error_text_inline = flag
        self._help_text_inline = not flag

    def add_input(self, input_object):
        self.inputs.append(input_object)

    def add_layout(self, layout):
        self.layout = layout

    def render_layout(self, form, context, template_pack=TEMPLATE_PACK):
        """
        Returns safe html of the rendering of the layout
        """
        form.rendered_fields = set()
        form.crispy_field_template = self.field_template

        # This renders the specified Layout strictly
        html = self.layout.render(form, context, template_pack=template_pack)

        # Rendering some extra fields if specified
        if self.render_unmentioned_fields or self.render_hidden_fields or self.render_required_fields:
            fields = tuple(form.fields.keys())
            left_fields_to_render = list_difference(fields, form.rendered_fields)
            for field in left_fields_to_render:
                if (
                    self.render_unmentioned_fields
                    or (self.render_hidden_fields and form.fields[field].widget.is_hidden)
                    or (self.render_required_fields and form.fields[field].widget.is_required)
                ):
                    html += render_field(field, form, context, template_pack=template_pack)

        return mark_safe(html)

    def get_attributes(self, template_pack=TEMPLATE_PACK):  # noqa: C901
        """
        Used by crispy_forms_tags to get helper attributes
        """
        items = {
            "disable_csrf": self.disable_csrf,
            "error_text_inline": self.error_text_inline,
            "field_class": self.field_class,
            "field_template": self.field_template or "%s/field.html" % template_pack,
            "form_method": self.form_method.strip(),
            "form_show_errors": self.form_show_errors,
            "form_show_labels": self.form_show_labels,
            "form_tag": self.form_tag,
            "help_text_inline": self.help_text_inline,
            "include_media": self.include_media,
            "label_class": self.label_class,
            "use_custom_control": self.use_custom_control,
        }

        if template_pack == "bootstrap4":
            if "form-horizontal" in self.form_class.split():
                bootstrap_size_match = re.findall(r"col(-(xl|lg|md|sm))?-(\d+)", self.label_class)
                if bootstrap_size_match:
                    offset_pattern = "offset%s-%s"
                    items["bootstrap_checkbox_offsets"] = [
                        offset_pattern % (m[0], m[-1]) for m in bootstrap_size_match
                    ]
        else:
            bootstrap_size_match = re.findall(r"col-(lg|md|sm|xs)-(\d+)", self.label_class)
            if bootstrap_size_match:
                offset_pattern = "col-%s-offset-%s"
                items["bootstrap_checkbox_offsets"] = [offset_pattern % m for m in bootstrap_size_match]

        items["attrs"] = {}
        if self.attrs:
            items["attrs"] = self.attrs.copy()
        if self.form_action:
            items["attrs"]["action"] = self.form_action.strip()
        if self.form_id:
            items["attrs"]["id"] = self.form_id.strip()
        if self.form_class:
            items["attrs"]["class"] = self.form_class.strip()
        if self.form_group_wrapper_class:
            items["attrs"]["form_group_wrapper_class"] = self.form_group_wrapper_class

        items["flat_attrs"] = flatatt(items["attrs"])

        if self.inputs:
            items["inputs"] = self.inputs
        if self.form_error_title:
            items["form_error_title"] = self.form_error_title.strip()
        if self.formset_error_title:
            items["formset_error_title"] = self.formset_error_title.strip()

        for attribute_name, value in self.__dict__.items():
            if (
                attribute_name not in items
                and attribute_name not in ["layout", "inputs"]
                and not attribute_name.startswith("_")
            ):
                items[attribute_name] = value

        return items 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) @register.filter
def optgroups(field):
    """
    A template filter to help rendering of fields with optgroups.

    Returns:
        A tuple of label, option, index

        label: Group label for grouped optgroups (`None` if inputs are not
               grouped).
        option: A dict containing information to render the option::
            {
                "name": "checkbox_select_multiple",
                "value": 1,
                "label": 1,
                "selected": False,
                "index": "0",
                "attrs": {"id": "id_checkbox_select_multiple_0"},
                "type": "checkbox",
                "template_name": "django/forms/widgets/checkbox_option.html",
                "wrap_label": True,
            }
        index: Group index`
    """
    id_ = field.field.widget.attrs.get("id") or field.auto_id
    attrs = {"id": id_} if id_ else {}
    attrs = field.build_widget_attrs(attrs)
    values = field.field.widget.format_value(field.value())
    return field.field.widget.optgroups(field.html_name, values, attrs) class GroupedChoiceForm(forms.Form):
    choices = [
        (
            "Audio",
            [
                ("vinyl", "Vinyl"),
                ("cd", "CD"),
            ],
        ),
        (
            "Video",
            [
                ("vhs", "VHS Tape"),
                ("dvd", "DVD"),
            ],
        ),
        ("unknown", "Unknown"),
    ]
    checkbox_select_multiple = forms.MultipleChoiceField(widget=forms.CheckboxSelectMultiple, choices=choices)
    radio = forms.MultipleChoiceField(widget=forms.RadioSelect, choices=choices) class SampleForm5(forms.Form):
    choices = [
        (1, 1),
        (2, 2),
        (1000, 1000),
    ]
    checkbox_select_multiple = forms.MultipleChoiceField(widget=forms.CheckboxSelectMultiple, choices=choices)
    radio_select = forms.ChoiceField(widget=forms.RadioSelect, choices=choices)
    pk = forms.IntegerField() 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 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 render_field(  # noqa: C901
    field,
    form,
    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.
    :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, 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 advanced_layout():
def template_packs(request, settings):
def check_template_pack(node, template_pack): class SampleForm(forms.Form):
    is_company = forms.CharField(label="company", required=False, widget=forms.CheckboxInput())
    email = forms.EmailField(
        label="email", max_length=30, required=True, widget=forms.TextInput(), help_text="Insert your email"
    )
    password1 = forms.CharField(label="password", max_length=30, required=True, widget=forms.PasswordInput())
    password2 = forms.CharField(label="re-enter password", max_length=30, required=True, widget=forms.PasswordInput())
    first_name = forms.CharField(label="first name", max_length=5, required=True, widget=forms.TextInput())
    last_name = forms.CharField(label="last name", max_length=5, required=True, widget=forms.TextInput())
    datetime_field = forms.SplitDateTimeField(label="date time", widget=forms.SplitDateTimeWidget())

    def clean(self):
        super().clean()
        password1 = self.cleaned_data.get("password1", None)
        password2 = self.cleaned_data.get("password2", None)
        if not password1 and not password2 or password1 != password2:
            raise forms.ValidationError("Passwords dont match")

        return self.cleaned_data
from django import forms
from django.template.base import Template
from django.template.context import Context
from django.test import SimpleTestCase
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout
from crispy_forms.templatetags.crispy_forms_filters import optgroups
from crispy_forms.tests.forms import GroupedChoiceForm, SampleForm5
from crispy_forms.tests.utils import contains_partial
from crispy_forms.utils import list_difference, list_intersection, render_field
from .conftest import only_bootstrap4
from .forms import SampleForm
from .utils import parse_expected, parse_form
    from django.forms.boundfield import BoundField

def test_optgroup_filter_nested():
    form = GroupedChoiceForm({"checkbox_select_multiple": ["cd", "vhs"]})
    form.as_p()
    groups = optgroups(form["checkbox_select_multiple"])
    audio, video, unknown = groups
    label, options, index = audio
    assert label == "Audio"
    assert options == [
        {
            "value": "vinyl",
            "type": "checkbox",
            "attrs": {"id": "id_checkbox_select_multiple_0_0"},
            "index": "0_0",
            "label": "Vinyl",
            "template_name": "django/forms/widgets/checkbox_option.html",
            "name": "checkbox_select_multiple",
            "selected": False,
            "wrap_label": True,
        },
        {
            "value": "cd",
            "type": "checkbox",
            "attrs": {"checked": True, "id": "id_checkbox_select_multiple_0_1"},
            "index": "0_1",
            "label": "CD",
            "template_name": "django/forms/widgets/checkbox_option.html",
            "name": "checkbox_select_multiple",
            "selected": True,
            "wrap_label": True,
        },
    ]
    assert index == 0
    label, options, index = video
    assert label == "Video"
    assert options == [
        {
            "value": "vhs",
            "template_name": "django/forms/widgets/checkbox_option.html",
            "label": "VHS Tape",
            "attrs": {"checked": True, "id": "id_checkbox_select_multiple_1_0"},
            "index": "1_0",
            "name": "checkbox_select_multiple",
            "selected": True,
            "type": "checkbox",
            "wrap_label": True,
        },
        {
            "value": "dvd",
            "template_name": "django/forms/widgets/checkbox_option.html",
            "label": "DVD",
            "attrs": {"id": "id_checkbox_select_multiple_1_1"},
            "index": "1_1",
            "name": "checkbox_select_multiple",
            "selected": False,
            "type": "checkbox",
            "wrap_label": True,
        },
    ]
    assert index == 1
    label, options, index = unknown
    assert label is None
    assert options == [
        {
            "value": "unknown",
            "selected": False,
            "template_name": "django/forms/widgets/checkbox_option.html",
            "label": "Unknown",
            "attrs": {"id": "id_checkbox_select_multiple_2"},
            "index": "2",
            "name": "checkbox_select_multiple",
            "type": "checkbox",
            "wrap_label": True,
        }
    ]
    assert index == 2


def test_optgroup_filter():
    form = SampleForm5({"checkbox_select_multiple": "1"})
    groups = optgroups(form["checkbox_select_multiple"])
    group = groups[0]
    label, option, index = group
    assert label is None
    assert option == [
        {
            "name": "checkbox_select_multiple",
            "value": 1,
            "label": 1,
            "selected": True,
            "index": "0",
            "attrs": {"id": "id_checkbox_select_multiple_0", "checked": True},
            "type": "checkbox",
            "template_name": "django/forms/widgets/checkbox_option.html",
            "wrap_label": True,
        }
    ]
    assert index == 0

    form = SampleForm5({"checkbox_select_multiple": 1})
    groups = optgroups(form["checkbox_select_multiple"])
    group = groups[0]
    label, option, index = group
    assert label is None
    assert option == [
        {
            "name": "checkbox_select_multiple",
            "value": 1,
            "label": 1,
            "selected": True,
            "index": "0",
            "attrs": {"id": "id_checkbox_select_multiple_0", "checked": True},
            "type": "checkbox",
            "template_name": "django/forms/widgets/checkbox_option.html",
            "wrap_label": True,
        }
    ]
    assert index == 0

    form = SampleForm5({"checkbox_select_multiple": ""})

    groups = optgroups(form["checkbox_select_multiple"])