Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UpdateWithInlinesView not working with crispy_forms #216

Open
kishalaykundu opened this issue Jul 7, 2020 · 2 comments
Open

UpdateWithInlinesView not working with crispy_forms #216

kishalaykundu opened this issue Jul 7, 2020 · 2 comments

Comments

@kishalaykundu
Copy link

kishalaykundu commented Jul 7, 2020

I am using 'extra_views' to create and update a model 'Patient' and its related model 'PatientAddress'. I am also using 'crispy_forms' to beautify the html form. CreateWithInlinesView works fine. The UpdateWithInlinesView however does not seem to play nice with crispy_forms.
I have attached the requisite files, but I give a description of the problem below:

In 'update.html', I have the following lines:

DOES NOT WORK

`
{% csrf_token %}
<div class="form-row">
    <div class="form-group col-md-8 mb-0">
        {{ form.name|as_crispy_field }}
    </div>
    <div class="form-group col-md-4 mb-0">
        {{ form.phone|as_crispy_field }}
    </div>
</div>

{% for formset in inlines %} {% for addr in formset %}
<div class="form-row">
    <div class="form-group col-md-6 mb-0">
        {{ addr.line_1|as_crispy_field }}
    </div>
    <div class="form-group col-md-6 mb-0">
        {{ addr.line_2|as_crispy_field }}
    </div>
</div>

{% endfor %} {{ formset.management_form }} {% endfor %}

<div class="control-group text-right">
    <div class="controls">
        <button type="submit" class="btn btn-default btn-person"><i class="fas fa-save"></i> Save</button>
    </div>
</div>

WORKS:

`
{% csrf_token %}
<div class="form-row">
    <div class="form-group col-md-8 mb-0">
        {{ form.name|as_crispy_field }}
    </div>
    <div class="form-group col-md-4 mb-0">
        {{ form.phone|as_crispy_field }}
    </div>
</div>

{% for formset in inlines %}  {{ formset }} {% endfor %}

<div class="control-group text-right">
    <div class="controls">
        <button type="submit" class="btn btn-default btn-person"><i class="fas fa-save"></i> Save</button>
    </div>
</div>

The first form comes back to the update page whereas the second form works but is not at all aesthetically nice. I am not quite sure what to do with this or how to proceed from here. I apologize in advance if this is not the appropriate place to post about this.

code.zip

@danizen
Copy link

danizen commented Jul 28, 2021

I have this use case working lbasically like this:

    <form method="POST" action="{% url 'app-name:url-name' slug=object.slug %}">
        <div class="form-horizontal">
            {% crispy form %}
        </div>
        {% crispy formset %}
    </form>

I do basically the same thing again and again (e.g. the pattern above is repeated many times in my application).

The trick to making it work with InlineFormSet is to declare a form helper:

from crispy_forms.helper import FormHelper

class SomeFormSetHelper(FormHelper):
    """A crispy helper for the SomeInlineFormSet"""
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.form_tag = False
        self.disable_csrf = True
        self.template = 'table_inline_formset.html'
        self.layout = Layout(
            Field('field1', hidden=True),
            'field2',
            Field('field3', css_class='repeat-date'),
            Field('field4', css_class='repeat-user'),
            'field5'
        )

Then in the method construct_formset or construct_inlines, depending on which view you are using, you can associate the helper with the formset:

        formset.helper = SomeFormSetHelper()
        for form in formset:
            # munge fields if needed, an example:
            form.fields['DELETE'].widget.attrs['title'] = 'Delete on Save'
        return formset

Looks like I build a custom table version of inline formset, table_inline_formset.html to achieve what I wanted in a table view. I think the template below was about not showing the empty formset and then using jquery to duplicate it with a button - but my application is a little old now.

{% load crispy_forms_tags %}
{% load crispy_forms_utils %}
{% load crispy_forms_field %}
{% load crispy_forms_filters %}

{% specialspaceless %}
{% if formset_tag %}
<form {{ flat_attrs|safe }} method="{{ form_method }}" {% if formset.is_multipart %} enctype="multipart/form-data"{% endif %}>
{% endif %}
    {% if formset_method|lower == 'post' and not disable_csrf %}
        {% csrf_token %}
    {% endif %}

    {% if formset.non_form_errors %}
        {{ formset|as_crispy_errors }}
    {% endif %}

    <div>
        {{ formset.management_form|crispy }}
    </div>

    <table{% if form_id %} id="{{ form_id }}_table"{% endif%} class="table table-striped table-condensed">
        <thead>
            {% if formset.readonly and not formset.queryset.exists %}
            {% else %}
                <tr>
                    {% for field in formset.empty_form %}
                        {% if field.label and not field.is_hidden %}
                            <th for="{{ field.auto_id }}" class="control-label {% if field.field.required %}requiredField{% endif %}">
                                {{ field.label|safe }}{% if field.field.required %}<span class="asteriskField">*</span>{% endif %}
                            </th>
                        {% endif %}
                    {% endfor %}
                </tr>
            {% endif %}
        </thead>

        <tbody>
            <tr class="hidden empty-form">
                {% for field in formset.empty_form %}
                    {% include 'bootstrap3/field.html' with tag="td" form_show_labels=False %}
                {% endfor %}
            </tr>

            {% for form in formset %}
                {% if form_show_errors and not form.is_extra %}
                    {% include "bootstrap3/errors.html" %}
                {% endif %}

                <tr>
                    {% for field in form %}
                        {% include 'bootstrap3/field.html' with tag="td" form_show_labels=False %}
                    {% endfor %}
                </tr>
            {% endfor %}
        </tbody>
    </table>

    {% include "bootstrap3/inputs.html" %}

{% if formset_tag %}</form>{% endif %}
{% endspecialspaceless %}

@oesah
Copy link

oesah commented Jan 31, 2024

In case someone needs this, here is my solution:

class MyFormHelper(FormHelper):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.helper = FormHelper()
        self.helper.form_tag = False
        self.helper.layout = Layout(
            "field",
        )

class PlaceMetaInline(InlineFormSetFactory):
    ...
    def construct_formset(self):
        formset = super().construct_formset()
        formset.helper = forms.MyFormHelper().helper
        return formset

# In template
{% for inline_form in inlines %}
  {% crispy inline_form inline_form.helper %}
{% endfor %}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants