2025, Oct 16 09:36

Flask ऐप में fetch/JSON से क्लास सूची बिना रीलोड अपडेट

जानें कैसे Flask में fetch और JSON से sqlite3 लिखने के बाद बिना पेज रीलोड कक्षाओं की सूची जैसे सेक्शन अपडेट करें; @login_required के साथ credentials शामिल करें.

जब कोई Flask view ऐसा टेम्पलेट रेंडर करता है जो डेटाबेस की स्थिति पर निर्भर है, तो लिखने के बाद बदलाव दिखाने का सबसे आसान तरीका पूरे पेज को फिर से लोड करना होता है। यह चलता है, लेकिन अगर आपको सिर्फ एक हिस्से—जैसे कक्षाओं की सूची—को अपडेट करना हो, तो यह अनुभव टूटता हुआ और अक्षम लगता है। उद्देश्य है कि नया डेटा sqlite3 में सहेजा जाए और बिना पूरा पेज रीलोड किए सिर्फ संबंधित खंड को ताज़ा किया जाए।

समस्या का सेटअप

इस उपयोग परिदृश्य में शामिल डेटाबेस संरचना यह है:

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

एकल मॉडल दिखाने वाला पेज इस तरह की रूट से परोसा जाता है। टेम्पलेट को कई वैरिएबल्स मिलते हैं—जिनमें कक्षाओं की सूची और इमेजेस भी शामिल हैं—जो view के अंदर इकट्ठे किए जाते हैं:

@app.route("/model/<model_id>", methods=["POST", "GET"])
@login_required
def show_model(model_id):
    # ... एप्लिकेशन लॉजिक छोड़ा गया ...
    with sqlite3.connect("database.db") as conn:
        # ... नीचे दिए गए वैरिएबल्स को भरने वाली क्वेरियाँ ...
        return render_template(
            "train.html",
            categories=ALL_GROUPS,
            model_id=model_id,
            model=mdl,
            classes=class_rows,
            images=pics,
            istrained=trained_flag,
            test_image=probe_img,
        )

नई कक्षा बनाने के लिए, एक अलग रूट डेटाबेस में लिखता है और फिर मॉडल पेज पर रीडायरेक्ट कर देता है। यह रीडायरेक्ट नया डेटा लेने के लिए पूरे पेज को दोबारा लोड करने पर मजबूर करता है।

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

रीडायरेक्ट लौटाने से पूरा पेज रीलोड हो जाता है—यह काम तो करता है, मगर वांछित UX नहीं है। खाली उत्तर या अलग स्टेटस कोड लौटाने से भी फायदा नहीं होगा, क्योंकि पेज के लिए डेटा तैयार करने का काम पहले रूट में होता है।

असल रुकावट कहाँ है

Flask सर्वर-साइड टेम्पलेट्स रेंडर करता है और पूरा HTML रिस्पॉन्स भेजता है। इसमें अपने आप ब्राउज़र तक आंशिक अपडेट पहुँचाने का कोई अंतर्निहित तरीका नहीं है। ब्राउज़र की ओर से क्लाइंट‑साइड अनुरोध के बिना, सर्वर पेज के केवल किसी हिस्से को अपडेट नहीं कर सकता। यही वजह है कि रीडायरेक्ट टेम्पलेट को फिर चलाता है और सब कुछ रीलोड हो जाता है, जबकि बिना रीडायरेक्ट वाले उत्तर से कक्षाओं वाला सेक्शन खुद‑ब‑खुद ताज़ा नहीं होता।

Flask में ऐसा कोई विशेष फ़ंक्शन नहीं है जो पेज रीलोड किए बिना ब्राउज़र से सर्वर तक डेटा भेज दे।

बिना पूरा रीलोड किए सामग्री बदलने के लिए, ब्राउज़र को सर्वर पर असिंक्रोनस कॉल करनी होती है और फिर वहीं पर DOM अपडेट करना होता है। इसके लिए JavaScript जरूरी है। व्यवहारिक तरीका है कि मानक fetch() API का उपयोग करके Flask को अनुरोध भेजें और डेटा (जैसे JSON) लें, जिसे पेज पर लागू किया जा सके। बदले में Flask request.method और request.is_json देख सकता है और jsonify(...) के साथ जवाब दे सकता है ताकि ब्राउज़र परिणाम को पार्स कर उपयोग कर सके। यदि view @login_required से सुरक्षित है, तो fetch अनुरोध में कुकीज़ ले जाने के लिए credentials शामिल करना होगा।

समाधान: fetch() के साथ async कॉल और JSON उत्तर

नीचे दिया गया उदाहरण दिखाता है कि बिना रीलोड किए पेज से Flask को अनुरोध कैसे भेजें, JSON स्थिति कैसे प्राप्त करें और उसे वहीं पर प्रदर्शित करें। साथ ही, यह भी बताता है कि fetch() में credentials: 'include' जोड़कर @login_required के साथ इसे कैसे चलाया जाए।

from flask import Flask, request, jsonify, render_template_string, redirect
from flask_login import LoginManager, UserMixin, login_required, login_user, logout_user
class Account(UserMixin):
    def __init__(self, uid, mail, secret):
        self.id = uid
        self.email = mail
        self.password = secret
# कुंजी Account.id से मेल खाती है
account_store = {
    '007': Account('007', 'james_bond@mi6.gov.uk', 'license_to_kill')
}
srv = Flask(__name__)
srv.secret_key = "super secret string"  # वास्तविक ऐप में बदलें
auth = LoginManager()
auth.init_app(srv)
@auth.user_loader
def load_account(user_id):
    print('load_account:', user_id, account_store.get(user_id))
    return account_store.get(user_id)
@srv.route('/login')
def do_login():
    user = account_store.get('007')
    print('login:', user)
    login_user(user)
    print('redirect:', '/')
    return redirect('/')
@srv.route('/logout')
@login_required
def do_logout():
    logout_user()
    return redirect('/')
@srv.route('/')
def home():
    return render_template_string("""
<script type="text/javascript">
function pushData(modelId, httpMethod) {
    fetch("/add_model/" + modelId, {
        method: httpMethod,  // "POST" या "GET"
        // body: JSON.stringify({"username": "example"}), // अगर आपको body में JSON भेजना हो
        headers: {
            // "Content-Type": "application/x-www-form-urlencoded", // <form> के लिए
            "Content-Type": "application/json", // Flask में request.is_json सक्षम करता है
            // "X-Requested-With": "XMLHttpRequest" // सामान्य AJAX हेडर
        },
        credentials: 'include', // @login_required के लिए कुकीज़
    }).then((resp) => {
        if (!resp.ok) {
            throw new Error(`HTTP error! Status: ${resp.status}`);
        }
        return resp.json(); // JSON अपेक्षित
        // return resp.text(); // अगर Flask text/HTML लौटाता है
    }).then((payload) => {
        document.getElementById("statusBox").innerHTML = `model #${modelId}: ${payload['status']}`;
    }).catch((err) => {
        console.log('Fetch problem: ' + err.message);
        document.getElementById("statusBox").innerHTML = 'Fetch problem: ' + err.message;
    });
}
</script>
{% if current_user.is_authenticated %}
<p>Logged in as: {{ current_user.id }} <a href="{{ url_for('do_logout') }}">Logout</a></p>
{% else %}
<p>You are not logged in. <a href="{{ url_for('do_login') }}">Login</a></p>
{% endif %}
{% for mid in range(1, 6) %}
<button onclick="pushData({{ mid }}, 'POST')">Add model #{{ mid }} (POST)</button>
<button onclick="pushData({{ mid }}, 'GET')">Add model #{{ mid }} (GET)</button>
<br/>
{% endfor %}
<div id="statusBox"></div>
""")
@srv.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__":
    srv.run(host='0.0.0.0')

यह पैटर्न बिना पूरा रीलोड किए कंटेंट अपडेट करने का मूल तरीका है। ब्राउज़र fetch() से Flask रूट को कॉल करता है। Flask jsonify(...) के जरिए JSON लौटाता है। पेज उस JSON को पढ़कर DOM के केवल संबंधित हिस्से को अपडेट करता है। अगर पहुँच के लिए ऑथेंटिकेशन चाहिए, तो @login_required के लिए सर्वर सत्र सत्यापित कर सके—इसलिए credentials शामिल किए जाते हैं।

यह क्यों मायने रखता है

जब पेज एक ही रूट में जुटाए गए डेटा पर निर्भर होता है, तो रीडायरेक्ट ताज़गी तो सुनिश्चित करता है लेकिन पूरी रेंडरिंग दोहराने की कीमत पर। असिंक्रोनस कॉल्स सर्वर‑साइड लॉजिक को वैसा ही रखते हुए UI को अधिक सुचारु बनाती हैं। खास बात यह है कि request.method और request.is_json सर्वर को अनुरोध के प्रकार अलग‑अलग पहचानने देते हैं, और jsonify यह सुनिश्चित करता है कि ब्राउज़र के उपयोग के लिए जवाब सही तरह फ़ॉर्मैट हो।

व्यावहारिक निष्कर्ष

पूरे पेज को रीलोड किए बिना सामग्री बदलने के लिए, ब्राउज़र से असिंक्रोनस अनुरोध शुरू करवाएँ। fetch() का उपयोग कर Flask रूट को कॉल करें, JSON लौटाएँ (या यदि आप HTML सम्मिलित करना चाहते हैं तो HTML), और पेज को वहीं पर अपडेट करें। यदि रूट्स @login_required से सुरक्षित हैं, तो fetch() में credentials जोड़ें। sqlite3-आधारित स्थिति पर निर्भर Flask ऐप में आंशिक अपडेट्स चाहिए हों, तो यही सरल और भरोसेमंद तरीका है।

यह लेख StackOverflow पर प्रश्न (लेखक: Burak) और furas के उत्तर पर आधारित है।