pythonhtmlsqliteflask

How to refresh items on a page without page refresh in Flask


I'm trying to add a script which directs me to the same page with refreshed content.

I'm using an sqlite3 database for my code and when i try it only works with the page refresh. This is one of my SQL structures.

CREATE TABLE classes (
    model_id TEXT NOT NULL,
    class_number INTEGER,
    class_name TEXT,
    FOREIGN KEY(model_id) REFERENCES models(model_id)
);

The problem is this is my flask route i use this kind of structure:

@app.route("/model/<model_id>",methods=["POST","GET"])
@login_required
def view_model(model_id):
...
with sqlite3.connect("database.db") as con:
.
.
.
return render_template("train.html",  
   categories=CATEGORIES, 
   model_id=model_id, 
   model=model,
   classes=classes,
   images=images,
   istrained=istrained,
   test_image=test_image
   )
images=images)

And when ijust need to change the SQL just like in this route i need to either redirect(f"/model/{model_id}") or return "",402.In the first case all the page reloads and since the sql change it successfully reloads all the page.Also when i try the second one nothing applies since the classes is defined in the first route.

@app.route("/add_class/<model_id>",methods=["POST","GET"])
@login_required
def add_class(model_id):
    with sqlite3.connect("database.db") as con:
        con.row_factory = sqlite3.Row
        db = con.cursor()
        result = db.execute("SELECT MAX(class_number) FROM classes WHERE model_id = ?", (model_id,)).fetchone()
        max_num = result[0] if result and result[0] is not None else 0
        default_num = int(max_num +1)
        default_name = "Class " + str(max_num + 1)
        db.execute("INSERT INTO classes (model_id,class_name,class_number) VALUES(?,?,?)",
        (model_id,default_name,default_num))
        con.commit()
    return redirect(f"/model/{model_id}")

I need to find a way to change the content but without all the page reloading.And want to know are there any ways to solve this problem without javascript.


Solution

  • Flask doesn't have special functions to send data from browser to server without reloading page.

    It needs to use JavaScript and standard function fetch() (or external modules like jQuery) to send data to serve and get result (without reloading paga) and eventually update only some part of page.

    fetch can use GET, POST or any other method. It can send text, HTML, JSON, image, etc.

    Flask can sends back HTML, JSON or even image and JavaScript can use it to update HTML on page.

    Flask can use request.method to detect if it is POST request, and request.is_json to check if it is JSON request (if only fetch sends header "Content-Type": "application/json")


    Minimal working code which use fetch to send POST to /add_model/<model_id>
    and Flask sends back JSON with {'status': ...} and JavaScript displays this status on page.

    I also added code which tests if it works with @login_required functions.
    It needs credentials: 'include' in fetch()

    from flask import Flask, request, jsonify, render_template_string, redirect
    from flask_login import LoginManager, UserMixin, login_required, login_user, logout_user
    
    
    class User(UserMixin):
        def __init__(self, id, email, password):
            self.id = id
            self.email = email
            self.password = password
    
    # dict key is the same as User.id
    users = {'007': User('007', 'james_bond@mi6.gov.uk', 'license_to_kill')}
    
    
    app = Flask(__name__)
    app.secret_key = "super secret string"  # Change this!
    
    
    login_manager = LoginManager()
    login_manager.init_app(app)
    
    @login_manager.user_loader
    def load_user(user_id):
        print('load_user:', user_id, users.get(user_id))
        return users.get(user_id)
    
    @app.route('/login')
    def login():
        user = users.get('007')
        print('login:', user)
    
        login_user(user)
    
        print('redirect:', '/')
        return redirect('/')
    
    
    @app.route("/logout")
    @login_required
    def logout():
        logout_user()
        return redirect('/')
    
    
    @app.route('/')
    def index():
    
        return render_template_string("""
    
    <script type=text/javascript>
    
    function send_data(model_id, method) {
        fetch("/add_model/" + model_id, {
            method: method,  // "POST"
            // body: JSON.stringify({"username": "example" }),  // if you need send JSON data in body
            headers: {
                // "Content-Type": "application/x-www-form-urlencoded",  // sending <form>
                "Content-Type": "application/json",  // if you want to use `reques.is_json` in Flask
                // "X-Requested-With": "XMLHttpRequest"  // popular header for AJAX requests
            },
            credentials: 'include',  // use cookies to access @login_required functions
        }).then((response) => {
            if (!response.ok) { // same as response.status < 200 || >= 300
                throw new Error(`HTTP error! Status: ${response.status}`);
            }
            return response.json();    // if you send JSON data
            //return response.text();  // if you send text or HTML
        }).then((data) => {
            //console.log(data);  // display message in DevTool console
            document.getElementById("result").innerHTML = `model #${model_id}: ${data['status']}`;  // use ` ` to format string with variables
        }).catch((err) => {
            console.log('Fetch problem: ' + err.message);
            console.log(err)
            document.getElementById("result").innerHTML = 'Fetch problem: ' + err.message;
        });
    }
    </script>
    
    {% if current_user.is_authenticated %}
    <p>Logged in as: {{ current_user.id }} <a href="{{ url_for('logout') }}">Logout</a></p>
    {% else %}
    <p>You are not logged in. <a href="{{ url_for('login') }}">Login</a></p>
    {% endif %}
    
    {% for model_id in range(1, 6) %}
    <button onclick="send_data({{ model_id }}, 'POST')">Add model #{{ model_id }} (POST)</button>
    <button onclick="send_data({{ model_id }}, 'GET')">Add model #{{ model_id }} (GET)</button>
    </br>
    {% endfor %}
    
    <div id="result"></div>
    """)
    
    @app.route('/add_model/<model_id>', methods=['GET', 'POST'])
    @login_required
    def add_model(model_id):
        print(f'add_model: {model_id} | method: {request.method} | is_json: {request.is_json}')
    
        if request.method == 'POST' and request.is_json:
            return jsonify({'status': 'OK'})
        else:
            return jsonify({'status': f'Wrong Method {request.method}'})
    
    
    if __name__ == "__main__":
        #app.debug = True
        app.run(host='0.0.0.0') #, port=8000) #, debug=True)