Add a cancel button in Flask with Flask-WTF/WTForms

Viewed 2749

I would like to add a cancel button which goes back to previous page. My code is : forms.py:

from flask_wtf import Form
from wtforms import StringField, HiddenField, SubmitField
from wtforms.validators import DataRequired, Length, ValidationError


def _required(form, field):
    if not field.raw_data or not field.raw_data[0]:
        raise ValidationError('Field is required')

class OrganisationForm(Form):
    id = HiddenField('id', default="-1")
    name = StringField('name', validators=[DataRequired()])
    manager_name = StringField('manager_name')
    address = StringField('address', validators=[DataRequired()])
    city = StringField('city', validators=[DataRequired()])
    postal_code = StringField('postal_code', validators=[DataRequired(), Length(max=16)])
    province = StringField('province', validators=[Length(max=2, message="Can't exceed 2 characters")])
    country = StringField('country', validators=[DataRequired()])
    submit = SubmitField('Add')
    cancel = SubmitField('Cancel')

and template page:

{% block content %}
<div class="content-section">
  {{ utils.flashed_messages() }}
  <div class="center">

      {% if add_orgnisation %}
                <h1>add an organisation</h1>
            {% else %}
                <h1>Edit an organisation</h1>
      {% endif %}
    <br/>
    <br/>
    {{ wtf.quick_form(form,novalidate=True) }}
  </div>
</div>
{% endblock %}

views,py

@orgs.route('/organisations/org_new')
@login_required
def org_new():
    add_orgnisation = True
    form = OrganisationForm()
    return render_template("organisations/organisation_form.html", form=form, title="New Organisation", edit=False, add_orgnisation=add_orgnisation)

I have a 405 error when I click on cancel button: Method not allowed.

Where is my mistake? and what should I add to have a go back previous page when I click on Cancel?

thanks

3 Answers

I have a 405 error when I click on cancel button: Method not allowed. Where is my mistake?

When you submit the form, the data will be sent as a POST request (because the rendered form element will use <form method="post">). However, your view function only allow GET request as default, to accept POST request, you have to specified the methods parameter like this:

@orgs.route('/organisations/org_new', methods=['GET', 'POST'])  # <--
@login_required
def org_new():
   # ...

and what should I add to have a go back previous page when I click on Cancel?

Any field created with SubmitField will rendered as a submit button (<input type="submit">), so it will submit the form when you click it.

To make it go back to the previous page when you click the cancel button, there are normally two methods to achieve this:

1. Catch the button submit in view function

Like the method in Ben's answer. You can just catch the submit then redirect the user to previous page:

@orgs.route('/organisations/org_new', methods=['GET', 'POST'])
@login_required
def org_new():
    if request.method == 'POST':
        if form.cancel.data:  # if cancel button is clicked, the form.cancel.data will be True
            return redirect(url_for('previous_page_view_name'))
    # ...

P.S. Since you have already set novalidate=True in wtf.quick_form, you don't need to set render_kw={'formnovalidate': True} on the cancel button in form class.

2. Create a <a> button instead of a cancel field

You can create a normal <a> element as cancel button (class="btn btn-secondary") and fill the href parameter with the previous page's URL (then you don't need to add cancel field in your form class). In this way, you can't use wtf.quick_form(), instead you will need to render each field manually with wtf.form_field():

<form method="post">
    {{ wtf.form_field(form.id) }}
    {{ wtf.form_field(form.name) }}
    {{ wtf.form_field(form.manager_name) }}
    {{ wtf.form_field(form.address) }}
    ...
    {{ wtf.form_field(form.submit) }}
    <a href="{{ url_for('previous_page_view_name') }}" class="btn btn-secondary">Cancel</a>
</form>

Here's how this works for me.

In my form...

btn_cancel = SubmitField(label='Cancel',
                         render_kw={'formnovalidate': True})

In my Python code (views.py in your case)...

if request.method == 'POST':
    if form.btn_cancel.data:
        return redirect(url_for('data'))

Last minute edit: Grey Li's note below about allowing your view function to accept POST requests is also important. Make sure you check it out.

No, I'm using the TimeField to enforce the capturing of a valid Time. I haven't tried to change that to a normal text field, so have gone with the Cancel link option above instead. My preference is still to have a Cancel button though.

Related