javascriptformsflaskfetch-api

Flash Flask messages with a fetch request from JavaScript


I've developed a cafe app using flask. I watched like 2 or 3 tutorials, and started to create as much as I could. Following this code from a form

<!-- login.html -->
<body>
    <h1>Login</h1>
    <div>
        {% for m in get_flashed_messages() %}
        <div>{{ m }}</div>
        {% endfor %}
    </div>

    <form method="POST">
        <label for="email">Email</label>
        <input type="text" name="email" id="email">
        <label for="password">Password</label>
        <input type="password" name="password" id="password">
        <input type="submit" id="submit" value="Login">
    </form>
</body>

and controller like this:

from flask import *

app = Flask(__name__)
app.secret_key = 'secret'
app.debug = True

@app.route("/", methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        email = request.form.get('email')
        pwd = request.form.get('password')
        
        if email and pwd:
            flash("Success!")
            return redirect(url_for('home'))
        else:
            flash("Email and password required!")
    return render_template('login.html')

@app.route("/home/")
def home():
    return render_template('home.html')

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

When the form is not complete, after click submit button, the page "reloads" and displays the flashed message as expected in the same page, and also when is complete, displays the flashed message in home.html. That's Ok.

<!-- home.html -->
<!DOCTYPE html>
<html>
<body>
    <div>
        {% for m in get_flashed_messages() %}
        <div>{{ m }}</div>
        {% endfor %}
    </div>
    <h1>Logging successful</h1>
</body>
</html>

However, the issue is, when I try to do the "same" but from JavaScript with a fetch and JSON requests like this:

    <script>
        window.onload = function () {
            let email = document.getElementById('email');
            let pwd = document.getElementById('password');
            let submit = document.getElementById('submit');
            submit.addEventListener('click', async function () {
                let data = {'email': email.value,
                    'password': pwd.value
                };

                let request = {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json'
                    },
                    body: JSON.stringify(data)
                };
                let response = await fetch("/", request)
                console.log(response);
            });
        }
    </script>

It doesnt "reload" the page, more over it doesnt show the flashed messages as I would expect. I just want to submit data from JavaScript and, the flash messages to work as expected, show after the request is complete, as it were a form, but without a form. Is it that possible in first place? It's been a month and I cannot figure out how to do it, I'm giving up on this.

BTW I changed the <form> to <div> in order to send it not as form, and of course, read request.json for JS thing

I've read the documentation, but it only shows an example with form html method, that is what I wrote first.

I've also tried to see what is the response like let response = await fetch("/", data).then(r => r.text()) and it logs the HTML that **does contain** the flashed message. I mean, it's just right there, but it doesn't do anything about it, nor show the flashed messages. Also, I read that you can manually redirect with if(response.redirected) window.location.href = response.url And it redirects, but again no flash message are shown.

I wouldn't like to rewrite the HTML code like


document.open();
document.write(html);
document.close();

Because is only overlapping the HTML, and when reloads is not the actual page that was redirected in first place.


Solution

  • After one night of sleep, I only changed the header of the request to manual and if the type were opaqueredirect then redirect manually.

                submit.addEventListener('click', async function () {
                let data = {'email': email.value,
                    'password': pwd.value
                };
    
                let request = {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json'
                    },
                    redirect: 'manual', //This line
                    body: JSON.stringify(data)
                };
                let response = await fetch("/", request);
                console.log(response)
                if (response.type == "opaqueredirect"){
                    window.location.href = response.url
                    window.location.reload()
                }
                
            });
    

    On browser I got HTTP status = 0, so after reading this it says that that empty response is just for frontend. Also the controller flash to a redirect always like return redirect(url_for('endpoint'))

        @app.route("/", methods=['GET', 'POST'])
        def login():
            if request.method == 'POST':
                if request.is_json:
                    email = request.json['email']
                    pwd = request.json['password']
                else:
                    email = request.form.get('email')
                    pwd = request.form.get('password')
                print(email, pwd)
                if email and pwd:
                    flash("Success!")
                    return redirect(url_for('home'))
                else:
                    flash("Email and password required!")
                    return redirect(url_for('login'))
            return render_template('login.html')
    

    I don't know if this is the best solution in terms of security or scalability but it fits my needs. Better solutions are welcome :)