0%
Flask: إنشاء تطبيقات ويب خفيفة بلغة Python

Flask: إنشاء تطبيقات ويب خفيفة بلغة Python

اكتشف Flask لإنشاء تطبيقات ويب خفيفة بلغة Python: المسارات، وقوالب Jinja2، والنماذج، وواجهات API بصيغة JSON، وblueprints، والنشر في بيئة الإنتاج.

I

InSkillCoach

· دقيقة

Flask: إنشاء تطبيقات ويب خفيفة بلغة Python

Flask هو إطار عمل ويب مصغّر (micro-framework) للغة Python: يوفّر الحد الأدنى الضروري لبناء تطبيق ويب (التوجيه، والقوالب، ومعالجة الطلبات) ويترك لك حرية اختيار الباقي (قاعدة البيانات، والمصادقة، والنماذج). هذه الفلسفة البسيطة تجعله نقطة انطلاق ممتازة لتعلّم تطوير الويب بلغة Python، مع بقائه قويًا بما يكفي لتطبيقات الإنتاج. يرافقك هذا الدليل من التثبيت حتى النشر.

التثبيت وأبسط تطبيق

أنشئ بيئة افتراضية، ثم ثبّت Flask:

# Création et activation de l'environnement virtuel
python -m venv venv

# Windows
venv\Scripts\activate
# macOS / Linux
source venv/bin/activate

# Installation
pip install flask

يكفي بضعة أسطر لكتابة تطبيق Flask كامل. أنشئ ملفًا باسم app.py:

from flask import Flask

# إنشاء التطبيق
app = Flask(__name__)

# ربط عنوان URL بدالة (ما يسمى "view")
@app.route('/')
def accueil():
    return "Bonjour, Flask !"

if __name__ == '__main__':
    # debug=True : إعادة تحميل تلقائية ورسائل خطأ مفصّلة
    app.run(debug=True)

شغّل خادم التطوير:

python app.py
# * Running on http://127.0.0.1:5000

افتح http://127.0.0.1:5000 في المتصفح: ستعرض الصفحة «Bonjour, Flask !». لا تستخدم debug=True أبدًا في بيئة الإنتاج، فأداة تصحيح الأخطاء المدمجة تتيح تنفيذ شيفرة اعتباطية.

المسارات وطرق HTTP

يربط المزخرف @app.route عنوان URL بدالة. افتراضيًا، لا يستجيب المسار إلا لطلبات GET؛ ويتيح المعامل methods استخدام أفعال HTTP أخرى:

from flask import Flask, request

app = Flask(__name__)

@app.route('/contact', methods=['GET', 'POST'])
def contact():
    if request.method == 'POST':
        # معالجة إرسال النموذج
        return "Message reçu"
    # عرض النموذج (GET)
    return "Formulaire de contact"
الطريقةالاستخدام المعتاد
GETقراءة مورد، عرض صفحة
POSTإنشاء مورد، إرسال نموذج
PUTاستبدال مورد موجود
PATCHتعديل جزئي لمورد
DELETEحذف مورد

معاملات URL

تُعلن الأجزاء المتغيرة من عنوان URL بين قوسين زاويّين، وتُمرَّر كوسائط إلى الدالة:

# معامل نصي (افتراضي)
@app.route('/utilisateur/<nom>')
def profil(nom):
    return f"Profil de {nom}"

# معامل عدد صحيح : /article/42 يعمل، و /article/abc يعيد 404
@app.route('/article/<int:article_id>')
def article(article_id):
    return f"Article numéro {article_id}"

# معامل مسار : يقبل الشرطات المائلة
@app.route('/fichiers/<path:chemin>')
def fichier(chemin):
    return f"Chemin demandé : {chemin}"

المحوّلات المتاحة: string (الافتراضي)، int، float، path، uuid.

تُقرأ معاملات الاستعلام (?tri=date&page=2) باستخدام request.args:

@app.route('/recherche')
def recherche():
    terme = request.args.get('q', '')            # القيمة الافتراضية : ''
    page = request.args.get('page', 1, type=int)  # تحويل تلقائي
    return f"Recherche de « {terme} », page {page}"

قوالب Jinja2

يصبح إرجاع HTML داخل سلاسل Python نصية غير مقروء بسرعة. يدمج Flask محرك القوالب Jinja2: تُوضع ملفات HTML في مجلد templates/.

بنية المشروع:

mon_projet/
├── app.py
├── templates/
│   ├── base.html
│   └── index.html
└── static/
    └── style.css

وراثة القوالب

يحدد القالب الأساسي البنية المشتركة، وتملأ الصفحات الكتل (blocks):

<!-- templates/base.html -->
<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <title>{% block titre %}Mon Site{% endblock %}</title>
    <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
</head>
<body>
    <nav>
        <a href="{{ url_for('accueil') }}">Accueil</a>
    </nav>
    <main>
        {% block contenu %}{% endblock %}
    </main>
</body>
</html>
<!-- templates/index.html -->
{% extends "base.html" %}

{% block titre %}Accueil - Mon Site{% endblock %}

{% block contenu %}
<h1>Bienvenue, {{ nom }} !</h1>

{% if articles %}
<ul>
    {% for article in articles %}
    <li>{{ article.titre }} ({{ article.date }})</li>
    {% endfor %}
</ul>
{% else %}
<p>Aucun article pour le moment.</p>
{% endif %}
{% endblock %}

من جهة Python، تحمّل الدالة render_template القالب وتمرّر إليه المتغيرات:

from flask import render_template

@app.route('/')
def accueil():
    articles = [
        {"titre": "Premier article", "date": "2026-09-01"},
        {"titre": "Deuxième article", "date": "2026-09-10"},
    ]
    return render_template('index.html', nom="Alice", articles=articles)

يقوم Jinja2 تلقائيًا بتهريب (escape) HTML في المتغيرات، مما يحمي من هجمات XSS.

الملفات الثابتة

تُوضع أوراق الأنماط والسكربتات والصور في مجلد static/. وتولّد الدالة url_for عناوين URL الخاصة بها:

<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
<img src="{{ url_for('static', filename='images/logo.png') }}" alt="Logo">

استخدم url_for دائمًا بدل المسارات المكتوبة يدويًا: تبقى العناوين صحيحة حتى لو نُشر التطبيق تحت بادئة مسار.

النماذج وكائن request

تُقرأ بيانات النموذج المرسل بطريقة POST باستخدام request.form:

from flask import Flask, request, render_template

app = Flask(__name__)

@app.route('/inscription', methods=['GET', 'POST'])
def inscription():
    if request.method == 'POST':
        nom = request.form.get('nom', '').strip()
        email = request.form.get('email', '').strip()

        # تحقق أدنى
        if not nom or not email:
            return render_template('inscription.html',
                                   erreur="Tous les champs sont obligatoires")

        # هنا : الحفظ في قاعدة البيانات
        return f"Bienvenue, {nom} !"

    return render_template('inscription.html')
<!-- templates/inscription.html -->
{% extends "base.html" %}
{% block contenu %}
{% if erreur %}<p class="erreur">{{ erreur }}</p>{% endif %}
<form method="post">
    <input type="text" name="nom" placeholder="Votre nom">
    <input type="email" name="email" placeholder="Votre email">
    <button type="submit">S'inscrire</button>
</form>
{% endblock %}

في المشاريع الحقيقية، تضيف مكتبة Flask-WTF التحقق من صحة البيانات والحماية من CSRF.

إعادة التوجيه ورسائل flash

بعد نجاح طلب POST، أعد توجيه المستخدم (نمط «Post/Redirect/Get») واعرض رسالة مؤقتة باستخدام flash:

from flask import Flask, flash, redirect, url_for, render_template

app = Flask(__name__)
app.secret_key = "changez-moi-en-production"  # مطلوب لـ flash والجلسات

@app.route('/enregistrer', methods=['POST'])
def enregistrer():
    # ... المعالجة ...
    flash("Enregistrement réussi !", "succes")
    return redirect(url_for('accueil'))
<!-- Dans base.html, avant le bloc contenu -->
{% with messages = get_flashed_messages(with_categories=true) %}
    {% for categorie, message in messages %}
    <div class="flash flash-{{ categorie }}">{{ message }}</div>
    {% endfor %}
{% endwith %}

واجهة API بصيغة JSON مع jsonify

يناسب Flask واجهات REST API تمامًا. تحوّل الدالة jsonify قاموسًا إلى استجابة JSON مع ترويسة Content-Type الصحيحة:

from flask import Flask, jsonify, request

app = Flask(__name__)

TACHES = [
    {"id": 1, "titre": "Apprendre Flask", "faite": True},
    {"id": 2, "titre": "Créer une API", "faite": False},
]

@app.route('/api/taches', methods=['GET'])
def lister_taches():
    return jsonify(TACHES)

@app.route('/api/taches/<int:tache_id>', methods=['GET'])
def detail_tache(tache_id):
    tache = next((t for t in TACHES if t["id"] == tache_id), None)
    if tache is None:
        return jsonify({"erreur": "Tâche introuvable"}), 404
    return jsonify(tache)

@app.route('/api/taches', methods=['POST'])
def creer_tache():
    donnees = request.get_json()  # جسم الطلب بصيغة JSON
    if not donnees or "titre" not in donnees:
        return jsonify({"erreur": "Le champ 'titre' est requis"}), 400
    nouvelle = {"id": len(TACHES) + 1, "titre": donnees["titre"], "faite": False}
    TACHES.append(nouvelle)
    return jsonify(nouvelle), 201

اختبار سريع باستخدام curl:

curl http://127.0.0.1:5000/api/taches
# [{"faite": true, "id": 1, "titre": "Apprendre Flask"}, ...]

curl -X POST http://127.0.0.1:5000/api/taches \
     -H "Content-Type: application/json" \
     -d '{"titre": "Déployer"}'
# {"faite": false, "id": 3, "titre": "Déployer"}

التنظيم باستخدام blueprints

عندما يكبر التطبيق، يصبح الملف الواحد عصيًّا على الإدارة. تجمع الـ blueprints المسارات حسب المجال الوظيفي:

# blog/routes.py
from flask import Blueprint, render_template

blog_bp = Blueprint('blog', __name__, url_prefix='/blog')

@blog_bp.route('/')
def liste_articles():
    return render_template('blog/liste.html')

@blog_bp.route('/<int:article_id>')
def detail(article_id):
    return render_template('blog/detail.html', article_id=article_id)
# app.py
from flask import Flask
from blog.routes import blog_bp

app = Flask(__name__)
app.register_blueprint(blog_bp)
# أصبح المساران /blog/ و /blog/<id> فعّالين الآن

بنية معتادة لمشروع منظَّم:

mon_projet/
├── app.py
├── blog/
│   └── routes.py
├── auth/
│   └── routes.py
├── templates/
└── static/

الإعدادات ومتغيرات البيئة

لا تضع الأسرار (المفاتيح، وكلمات مرور قاعدة البيانات) في الشيفرة أبدًا. حمّلها من البيئة:

import os
from flask import Flask

app = Flask(__name__)

app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'dev-uniquement')
app.config['DATABASE_URL'] = os.environ.get('DATABASE_URL')
app.config['DEBUG'] = os.environ.get('FLASK_DEBUG', '0') == '1'

في بيئة التطوير، تحمّل الحزمة python-dotenv ملف .env تلقائيًا (ويجب استبعاده من مستودع Git عبر .gitignore):

# .env
SECRET_KEY=une-longue-chaine-aleatoire
DATABASE_URL=postgresql://user:pass@localhost/mabase
FLASK_DEBUG=1

النشر: gunicorn وnginx

خادم Flask المدمج أحادي العملية وغير آمن: لذا يقتصر استخدامه على التطوير. في الإنتاج، استخدم خادم WSGI مثل Gunicorn خلف خادم الويب nginx:

pip install gunicorn

# 4 عمليات عاملة، تستمع على المنفذ 8000
gunicorn --workers 4 --bind 127.0.0.1:8000 app:app

يعمل nginx كوكيل عكسي (reverse proxy): يستقبل حركة HTTPS، ويخدم الملفات الثابتة مباشرة، ويحوّل الباقي إلى Gunicorn:

server {
    listen 80;
    server_name exemple.com;

    location /static/ {
        alias /var/www/mon_projet/static/;
    }

    location / {
        proxy_pass http://127.0.0.1:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

تتولى منصات مثل Render وRailway وFly.io أتمتة هذا الإعداد إن لم ترغب في إدارة خادم.

Flask مقابل Django

المعيارFlaskDjango
الفلسفةإطار مصغّر، بسيطإطار متكامل، «البطاريات مشمولة»
منحنى التعلّمسلس، تتعلم تدريجيًا حسب الحاجةأشد انحدارًا، مفاهيم كثيرة منذ البداية
ORMحسب الاختيار (SQLAlchemy في الغالب)ORM مدمج في Django
الإدارةتُبنى يدويًا أو عبر إضافةواجهة إدارة تُولَّد تلقائيًا
المصادقةإضافة (Flask-Login)مدمجة
بنية المشروعحرةمفروضة ومتعارف عليها
الأنسب لـواجهات API، والخدمات المصغّرة، والمواقع الصغيرة، والنماذج الأوليةتطبيقات متكاملة مع لوحة إدارة

كلا الإطارين ناضج ويُستخدم في الإنتاج على نطاق واسع. اختر Flask لخفته ومرونته، واختر Django عندما تريد أكبر قدر من الميزات الجاهزة للاستخدام.

أفضل الممارسات

  • استخدم دائمًا بيئة افتراضية وملف requirements.txt (pip freeze > requirements.txt).
  • عطّل وضع debug في الإنتاج: فهو ثغرة أمنية خطيرة.
  • ولّد عناوين URL باستخدام url_for بدل كتابتها يدويًا.
  • خزّن الأسرار في متغيرات البيئة، وليس في الشيفرة المصدرية أبدًا.
  • نظّم تطبيقك باستخدام blueprints بمجرد أن يتجاوز عدد المسارات بضعة مسارات.
  • أعد رموز HTTP الصحيحة في واجهات API: 201 للإنشاء، و400 لطلب غير صالح، و404 لمورد غير موجود.
  • تحقق من جميع مدخلات المستخدم على جانب الخادم، حتى لو كان النموذج يتحقق منها على جانب العميل.

الخلاصة

يجسّد Flask فلسفة Python على أكمل وجه: بسيط في البداية، وقابل للتوسع مع نمو المشروع. أصبحت الآن قادرًا على إنشاء المسارات، وتوليد الصفحات باستخدام Jinja2، ومعالجة النماذج، وعرض واجهة API بصيغة JSON، وتنظيم شيفرتك باستخدام blueprints، والنشر باستخدام Gunicorn وnginx. الخطوة المنطقية التالية: ربط قاعدة بيانات عبر Flask-SQLAlchemy، وإدارة المصادقة عبر Flask-Login، وتأمين نماذجك عبر Flask-WTF.

للتعمق أكثر، راجع التوثيق الرسمي لـ Flask، فهو واضح وشامل بشكل لافت.

InSkillCoach

نبذة عن InSkillCoach

خبير في التدريب والتقنيات

مدرّب متخصص في التقنيات المتقدمة والذكاء الاصطناعي، تدعمه شركة GNeurone Inc.

الشهادات:

  • AWS Certified Solutions Architect – Professional
  • شهادات Google Cloud
  • Microsoft Certified: DevOps Engineer Expert
  • Certified Kubernetes Administrator (CKA)
  • CompTIA Security+
901
87

التعليقات

التعليقات مدعومة عبر GitHub Discussions

سجّل الدخول باستخدام GitHub للمشاركة في النقاش

تم نسخ الرابط!