jbengineer
jbengineer

Reputation: 19

Submitting 2 custom Crispy Forms with 1 button Django

I have 2 forms with the second form referencing the first form. I can get the forms to execute correctly using crispy forms, however, when I customize each form using helper = FormHelper() they will no longer submit together. Essentially one form is submitted and the other form thinks it is missing all of its input data.

Forms.py

    from django.forms import ModelForm, forms
from .models import Item, Item_dimensions
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout, Submit, Row, Column


# Create the form class.
class List_Item_Form(ModelForm):
    class Meta:
        model = Item
        exclude = ('id','creator','created_on','updated_on' )

    helper = FormHelper()
    helper.layout = Layout(
        Row(
            Column('item_name', css_class='form-group col-md-6 mb-0'),
            Column('price', css_class='form-group col-md-6 mb-0'),
            css_class='form-row'
        ),
        'short_description',
        'description',
        Row(
            Column('main_image', css_class='form-group col-md-2.5 mb-0'),
            Column('image_2', css_class='form-group col-md-2.5 mb-0'),
            Column('image_3', css_class='form-group col-md-2.5 mb-0'),
            Column('image_4', css_class='form-group col-md-2.5 mb-0'),
            Column('image_5', css_class='form-group col-md-2.5 mb-0'),
            css_class='form-row'
        ),
        'quantity'
    )
    helper.add_input(Submit('submit', 'Submit', css_class='btn-primary'))
    helper.form_method = 'POST'

class List_Item_Dimensions(ModelForm):
    class Meta:
        model = Item_dimensions
        exclude = ('id','item_id')
    helper = FormHelper()
    helper.layout = Layout(
            Row(
                Column('x_dim_mm', css_class='form-group col-md-4 mb-0'),
                Column('y_dim_mm', css_class='form-group col-md-4 mb-0'),
                Column('z_dim_mm', css_class='form-group col-md-4 mb-0'),
                css_class='form-row'
            ),
            'weight_grams',
            Submit('submit', 'add_listing')
        )
    helper.add_input(Submit('submit', 'Submit', css_class='btn-primary'))
    helper.form_method = 'POST'

I have tried removing

helper.add_input(Submit('submit', 'Submit', css_class='btn-primary'))

and using just one submit button but it didn't work

Views.py

@login_required
def AddListing(request):
    if request.method == 'POST':
        form = List_Item_Form(request.POST,request.FILES)
        dim_form = List_Item_Dimensions(request.POST)
        if form.is_valid() and dim_form.is_valid():
            itemName = form.cleaned_data['item_name']
            price = form.cleaned_data['price']
            s_desc = form.cleaned_data['short_description']
            desc = form.cleaned_data['description']
            quantity = form.cleaned_data['quantity']
            main_img = form.cleaned_data['main_image']
            image_2 = form.cleaned_data['image_2']
            image_3 = form.cleaned_data['image_3']
            image_4 = form.cleaned_data['image_4']
            image_5 = form.cleaned_data['image_5']
            x_dim = dim_form.cleaned_data['x_dim_mm']
            y_dim = dim_form.cleaned_data['y_dim_mm']
            z_dim = dim_form.cleaned_data['z_dim_mm']
            weight = dim_form.cleaned_data['weight_grams']
            current_user =  request.user
            

            model_instance = Item(creator=current_user, item_name=itemName, 
                                price=price,short_description=s_desc,
                                description=desc, quantity=quantity,
                                main_image=main_img, image_2=image_2, 
                                image_3=image_3, image_4 = image_4, 
                                image_5=image_5)
            dim_instance = Item_dimensions(x_dim_mm = x_dim,
                                            y_dim_mm =y_dim,
                                            z_dim_mm =z_dim, 
                                            weight_grams = weight)
            model_instance.save()
            # dim_instance.save(commit=False)
            dim_instance.item_id = model_instance
            dim_instance.save()
            return HttpResponseRedirect('/user_profile/items/')
    else:
        form = List_Item_Form()
        dim_form = List_Item_Dimensions()
    return render(request, 'store/add_listing.html', {'form': form,'dim_form':dim_form})

template.html

{% block content %}
<div>
    <h1> list a new item </h1>
{% load crispy_forms_tags %} 
{% csrf_token %}
<form method="post", enctype="multipart/form-data">{% csrf_token %}
    {% crispy form  %}
    {% crispy dim_form %}
    {% comment %} {{ form|crispy }}
    {{ dim_form|crispy }} {% endcomment %}
    <input name="submit" type="post" value="template button">
</form>
</div>

{% endblock content %}

Upvotes: 2

Views: 1163

Answers (2)

Ethan Posner
Ethan Posner

Reputation: 386

Expanding on @art's answer, here's what you probably want:

from django.forms import ModelForm
from .models import Item, Item_dimensions
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout, Submit, Row, Column


# Create the form class.
class List_Item_Form(ModelForm):
    class Meta:
        model = Item
        exclude = ('id', 'creator', 'created_on', 'updated_on')

    helper = FormHelper()
    helper.layout = Layout(
        Row(
            Column('item_name', css_class='form-group col-md-6 mb-0'),
            Column('price', css_class='form-group col-md-6 mb-0'),
            css_class='form-row'
        ),
        'short_description',
        'description',
        Row(
            Column('main_image', css_class='form-group col-md-2.5 mb-0'),
            Column('image_2', css_class='form-group col-md-2.5 mb-0'),
            Column('image_3', css_class='form-group col-md-2.5 mb-0'),
            Column('image_4', css_class='form-group col-md-2.5 mb-0'),
            Column('image_5', css_class='form-group col-md-2.5 mb-0'),
            css_class='form-row'
        ),
        'quantity'
    )
    helper.add_input(Submit('submit', 'Submit', css_class='btn-primary'))
    helper.form_tag = False  # add this
    helper.disable_csrf = True  # otherwise the crispy form will add an additional csrf token (optional)


class List_Item_Dimensions(ModelForm):
    class Meta:
        model = Item_dimensions
        exclude = ('id', 'item_id')
    helper = FormHelper()
    helper.layout = Layout(
            Row(
                Column('x_dim_mm', css_class='form-group col-md-4 mb-0'),
                Column('y_dim_mm', css_class='form-group col-md-4 mb-0'),
                Column('z_dim_mm', css_class='form-group col-md-4 mb-0'),
                css_class='form-row'
            ),
            'weight_grams',
            Submit('submit', 'add_listing')
    )
    helper.add_input(Submit('submit', 'Submit', css_class='btn-primary'))
    helper.form_tag = False  # add this
    helper.disable_csrf = True  # otherwise the crispy form will add an additional csrf token (optional)

And you'd render them inside of your template in the same way:

{% block content %}
<div>
    <h1> list a new item </h1>
    {% load crispy_forms_tags %}
    <form method="post", enctype="multipart/form-data">{% csrf_token %}
        {% crispy form  %}
        {% crispy dim_form %}
        <input name="submit" type="post" value="template button">
    </form>
</div>

{% endblock content %}

Also note that adding {% csrf_token %} simply adds an additional hidden form field similar to the following:

<input type="hidden" name="csrfmiddlewaretoken" value="uo5TUfy6PxVbDDKuEhRlJEri3wGOkOZuuZbHKoDWGOhA1O5zjk9RLSwfFcEuxQec">

Adding additional csrf tokens doesn't hurt, but also isn't necessary.

While not strictly related to the original question, I'd also like to give a word of advice. When dealing with ModelForms, Django does lots of magic behind the scenes. What this means is that you can save yourself the headache of manually grabbing all of your model's fields in your view to create a new model instance. I haven't tested this, but your view could most likely be greatly simplified to something similar this :

@login_required
def AddListing(request):
    if request.method == 'POST':
        form = List_Item_Form(request.POST,request.FILES)
        dim_form = List_Item_Dimensions(request.POST)
        if form.is_valid() and dim_form.is_valid():
            current_user = request.user

            model_instance = form.save(commit=False)
            model_instance.creator = current_user
            model_instance.save()

            dim_instance = dim_form.save(commit=False)
            dim_instance.item_id = model_instance
            dim_instance.save()
            return HttpResponseRedirect('/user_profile/items/')
    else:
        form = List_Item_Form()
        dim_form = List_Item_Dimensions()
    return render(request, 'store/add_listing.html', {'form': form, 'dim_form': dim_form})

Upvotes: 0

art
art

Reputation: 1402

{% crispy form %} automatically adds <form></form> tags unless otherwise you specify form_tag = False. See https://django-crispy-forms.readthedocs.io/en/latest/form_helper.html

Here you've already given an outer tag and hence the crispy tags inside it will create nested forms. This makes your HTML invalid. Check https://django-crispy-forms.readthedocs.io/en/latest/crispy_tag_forms.html#crispy-tag-forms

PS: (Haven't used django recently) - don't you need to include the helper and other customisations inside the constructor?

Upvotes: 2

Related Questions