Jakov
Jakov

Reputation: 1009

Flask & AJAX: Failed to load resource: the server responded with a status of 400 (BAD REQUEST)

I am making a Flask application for sending e-mails. It worked pretty fine until I decided to implement a little bit of AJAX for making it more interactive.

I constantly get 400 ERROR and I tried a lot of combinations, but noting worked.

Some people advised me that the problem is in CSRF token, but when I put @csrf.exempt decorator, it is still not working. Please help!

Here is the source code:

1) Flask:

from flask import Flask, render_template, url_for, redirect, request, jsonify
from flask_bootstrap import Bootstrap
from flask_sqlalchemy import SQLAlchemy
from flask_mail import Mail, Message
from flask_wtf.csrf import CSRFProtect
from wtforms import StringField, PasswordField, BooleanField, TextAreaField
from wtforms.validators import InputRequired, Email, Length, AnyOf, Regexp
from flask_wtf import FlaskForm

app = Flask(__name__)
app.config['SECRET_KEY'] = '**********'

mail = Mail(app)
Bootstrap(app)
csrf = CSRFProtect(app)

class LoginForm(FlaskForm):
    email = StringField('Email *', _name='email', validators=[InputRequired(), Email(message='I don\'t like your email.')], render_kw={"placeholder": "Please enter your email"})
    password = PasswordField('Password *', _name='password', validators=[InputRequired(), Length(min=5, max=10), AnyOf(['secret', 'password'])], render_kw={"placeholder": "Please enter your password"})

app.config.update(
    DEBUG = True,
    # EMAIL SETTINGS
    MAIL_SERVER = 'smtp.gmail.com',
    MAIL_PORT = 465,
    MAIL_USE_SSL = True,
    MAIL_USERNAME = 'jakov.kusic92@gmail.com',
    MAIL_PASSWORD = '********'
)

@csrf.exempt
@app.route('/', methods=['GET', 'POST'])
def mailForm():
    form = LoginForm()
    return render_template('loginForm.html', form=form)

@csrf.exempt
@app.route('/process', methods=['POST'])
def process():

    emailInput = request.form['email']
    passwordInput = request.form['password']


    str = 'Hello'
    msg = Message(
        subject=str,
        sender='jakov.kusic92@gmail.com',
        recipients=['jakov.kusic@gmail.com']
    )
    msg.body = """
    NEW USER INFO:
    Email: {}
    Password: {}
    """.format(emailInput, passwordInput)

    if emailInput and passwordInput:
        # mail.send(msg)
        return jsonify({'firstname' : emailInput, 'lastname' : passwordInput})

    return jsonify({'error' : 'Missing data!'})

if __name__ == '__main__':
    app.run(debug=True)

2) HTML:

{% extends "bootstrap/base.html" %}
{% import "bootstrap/wtf.html" as wtf %}

{% block title %}
WTForms
{% endblock %}

{% block head %}
    {{ super() }}
    <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
    <script src="https://code.jquery.com/jquery-2.2.4.js"></script>
    <script src="{{ url_for('static', filename='form.js') }}" type="text/javascript"></script>

{% endblock %}

{% block content %}
<div class="container">

    <br><br> <!-- br tags are here used for vertical alingment -->
    <div class="col-md-4 col-md-offset-4 col-sm-8 col-sm-offset-2">
        <div class="well">
            <br>
            <h1 class="text-center">Welcome to the database website</h1>
            <br>
            <h2>Please enter your data below:</h2>
            <form>
                <dl>
                    {{ wtf.quick_form(form)}}
                    <input type="submit" value="Login" class="btn btn-info">
                </dl>
            </form>
            <div class="row">
                <div class="col-md-12">
                    <p class="text-muted"><strong>*</strong> These fields are required.</p>
                </div>
            </div>
            <div id="successAlert" class="alert alert-success" role="alert" style="display:none;"></div>
            <div id="errorAlert" class="alert alert-danger" role="alert" style="display:none;"></div>
        </div>
    </div>
</div>
{% endblock %}

3) JS:

$('form').on('submit', function(event) {

    $.ajax({
        data : {
            firstnameInput : $('#firstnameInput').val(),
            lastnameInput : $('#lastnameInput').val(),
            emailInput : $('#emailInput').val(),
            passwordInput : $('#passwordInput').val(),
            messageInput : $('#messageInput').val()
        },
        type : 'POST',
        headers: {
            'Cache-Control': 'no-cache, no-store, must-revalidate',
            'Pragma': 'no-cache',
            'Expires': '0'
        },
        url : '/process',
        dataType: "json",
        contentType: "application/json"
    })
    .done(function(data) {
        $('#successAlert').text(data.firstname + data.lastname).show();
        $('#errorAlert').hide();
    });

    event.preventDefault();

});

THIS IS WHAT I GET ON CHROME DEV TOOLS:

enter image description here

Upvotes: 0

Views: 6652

Answers (1)

Artsiom Praneuski
Artsiom Praneuski

Reputation: 2329

You send ajax request with the following payload to /process URL:

    data : {
        firstnameInput : $('#firstnameInput').val(),
        lastnameInput : $('#lastnameInput').val(),
        emailInput : $('#emailInput').val(),
        passwordInput : $('#passwordInput').val(),
        messageInput : $('#messageInput').val()
    }

And you try to handle the following params in process route:

emailInput = request.form['email']
passwordInput = request.form['password']

But you don't send email and password keys in ajax request payload, you send emailInput and passwordInput params, so that's why Flask responds to you with 400 status code - there are no such params in POST body you're trying to use in the route function.

Your code should be:

@app.route('/process/', methods=['POST'])
def process():

    emailInput = request.form['emailInput']
    passwordInput = request.form['passwordInput']

That's it.

Also it's recommended to use "/" at the end of the url:

@app.route('/process/', methods=['POST'])

to avoid 302 redirect to /process/ url that Flask does.

It's recommended also to use request.form.get('param') instead of request.form['param'] because .get() function returns None if there is no specified key in dict instead of raising exception.

Upvotes: 1

Related Questions