اذهب إلى المحتوى

استخدام نماذج الويب والتحقق منها في فلاسك باستخدام الإضافة Flask-WTF


محمد الخضور

تمنح نماذج الويب web forms مثل الحقول النصيّة السطرية text fields وصناديق إدخال النصوص text areas المستخدمين القدرة على إرسال البيانات إلى التطبيق، سواءً احتوت هذه النماذج على قوائم منسدلة، أو أزرار انتقاء radio button، فسيستخدمها التطبيق في تنفيذ أمر ما، أو حتّى لإرسال محتوًى نصي كبير، إذ يُمنح المستخدم مثلًا في تطبيقات التواصل الاجتماعي حيزًا حيث يمكنه إضافة محتوى جديد إلى صفحته الشخصية.

يُعد فلاسك flask إطار عمل للويب مبني بلغة بايثون، ويتميز بكونه صغير الحجم وسهل المعالجة، ويوفّر أيضًا العديد من الأدوات والميزات التي من شأنها جعل إنشاء تطبيقات الويب في لغة بايثون أسهل، وبغية تصييّر نماذج الويب والتحقق من صحة مدخلاتها بأمانٍ ومرونة، سنستخدم إضافة فلاسك Flask-WTF التي تتيح لنا استخدام المكتبة WTForms المُتضمّنة نماذج الويب ذات واجهة المستخدم التفاعلية لاستخدامها في تطبيقات فلاسك.

إذًا، إضافة WTForms هي مكتبة من مكاتب بايثون، توفّر تصييرًا مرنًا لنماذج الويب، فمن الممكن استخدامها في تصيير الحقول النصيّة السطرية وصناديق إدخال النصوص متعددة الأسطر وصناديق كلمات المرور وأزرار الانتقاء وغيرها، كما أنّها توفّر آلية فعّالة للتحقّق من صحة البيانات التي يدخلها المستخدم بالاعتماد على ميّزة المُصادِقين المختلفين الهادفة للتأكّد من كون هذه البيانات تُطابق صيغًا وقواعد ومعايير محدّدة، فمثلًا من الممكن التحقق من كون المُستخدم قد أدخل فعلًا البيانات اللازمة في أحد الحقول المطلوب ملؤها من قبله، أو أنّ المدخلات بطول معيّن.

تستخدم مكتبة WTForms -إضافةً إلى مُساعدتنا على تأكيد البيانات وتقييدها بشروطٍ خاصّة- مفتاحًا مساعدًا لتحمينا من هجمات تزوير الطلب عبر المواقع Cross-site request forgery -أو اختصارًا CSRF- وهي نوع من الهجمات التي تُمكّن المخترق من تنفيذ أمور خطيرة غير مرغوبة في تطبيق الويب، متنكّرا على هيئة مُستخدم قد سجّل دخوله في التطبيق فعلًا، فقد تجبر الهجمات الناجحة المُستخدم الضحية على إرسال طلبات تغيير أساسية state-changing بغية تحويل أموال مثلًا إلى حساب المخترِق البنكي في أحد تطبيقات الصيرفة، أو تغيير عنوان البريد الإلكتروني الخاص بالمستخدم وهكذا دواليك، وفي حال كون الحساب الضحية حساب بصلاحيات مدير، فعندها ستكون هجمة تزوير الطلب CSRF قادرةً على اختراق كامل التطبيق.

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

مستلزمات العمل

قبل المتابعة في هذا المقال لا بُدّ من:

الخطوة الأولى - تثبيت فلاسك والإضافة Flask-WTF

سنعمل في هذه الخطوة على تثبيت كل من فلاسك والإضافة Flask-WTF والتي بدورها ستثبّت المكتبة WTForms تلقائيًا. لذلك، وبعد التأكّد من تفعيل البيئة الافتراضية، سنستخدم أمر تثبيت الحزم pip لتثبيت كل من فلاسك والإضافة Flask-WTF كما يلي:

(env)user@localhost:$  pip install Flask Flask-WTF

وبمجرّد انتهاء التثبيت بنجاح، سيظهر في السطر الأخير من الخرج ما يشبه التالي:

Successfully installed Flask-2.0.2 Flask-WTF-1.0.0 Jinja2-3.0.3 MarkupSafe-2.0.1 WTForms-3.0.0 Werkzeug-2.0.2 click-8.0.3 itsdangerous-2.0.1

ومنه نلاحظ أنّ المكتبة WTForms قد ثُبتّت أيضًا كونها تتبع للحزمة Flask-WTF، أما باقي الحزم المُثبّتة فهي من تلك التابعة لفلاسك نفسه، ومع نهاية هذه الخطوة نكون قد ثبتنا ما يلزم من حزم بايثون، وأصبح من الممكن الانتقال إلى الخطوة التالية المُتمثّلة بإعداد نموذج ويب.

الخطوة الثانية - إعداد النماذج

سنعدّ في هذه الخطوة نموذج ويب باستخدام مُدقّقين validators وحقول مستوردة من مكتبة WTForms.

إذ سنعدّ الحقول التالية:

  • "Title": وهو صندوق إدخال نصي لإدخال عنوان الدورة التدريبية.
  • "Description": وهو حقل نصي مُتعدّد الأسطر لإدخال توصيف الدورة التدريبية.
  • "Price": وهو حقل مُخصّص لإدخال رقم صحيح يُمثّل تكلفة الدورة التدريبية.
  • "Level": وهو حقل انتقاء بثلاث خيارات لتحديد مستوى الدورة التدريبية وهي: مبتدئ، متوسّط، متقدّم.
  • "Available": وهو مربع اختيار يشير لكون الدورة التدريبية متوفرّة ومتاحة حاليًا أم لا.

لذا، سننشئ بدايةً ضمن مجلد المشروع "flask_app" سننشئ ملفًا للنماذج باسم "forms.py"، ليتضمّن كافّة النماذج اللازمة للتطبيق:

(env)user@localhost:$ nano forms.py

إذ سيتضمّن هذا الملف صنفًا يمثّل نموذج الويب الذي نُعدّه، ولذلك سنضيف الاستيرادات التالية import إلى بداية الملف:

from flask_wtf import FlaskForm
from wtforms import (StringField, TextAreaField, IntegerField, BooleanField,
                     RadioField)
from wtforms.validators import InputRequired, Length

الآن، ولبناء نموذج ويب، سننشئ صنفًا فرعيًا من الصنف الأساسي FlaskForm الذي استوردناه أصلًا من الحزمة flask_wtf، كما لا بُدّ من تحديد الحقول المراد استخدامها في النموذج والتي سنستوردها من المكتبة wtforms، وهي:

  • StringField: وهو صندوق إدخال نصّي.
  • TextAreaField: وهو حقل إدخال نصّي مُتعدّد الأسطر.
  • IntegerField: وهو حقل مُخصّص لإدخال أعداد صحيحة.
  • BooleanField: وهو مربع اختيار.
  • RadioField: وهو حقل لإظهار أزرار الانتقاء ليتيح للمُستخدم اختيار إحداها.

هذه الحقول هي المسؤولة عن تصيّير البيانات وتحويلها إلى النمط المطلوب، إضافةً إلى تفويض المُدققين بغية التحقق من صحة البيانات المُدخلة.

استوردنا في السطر التالي من الشيفرة السابقة اثنين من المُدقّقين لنطبّقهما على الحقول الآنفة الذكر بما يضمن صحة مُدخلات المُستخدم:

from wtforms.validators import InputRequired, Length

إذ يُستخدم المُدقق InputRequired للتأكّد من كون المُستخدم قد أدخل فعلًا قيمًا في الحقول المطلوب ملؤها (الإجبارية)، والمُدقق Length يُستخدم للتأكّد من أن طول السلسلة النصية المُدخلة في حقل ما يُحقّق الحد الأدنى المطلوب من عدد المحارف، أو أنّه لم يتجاوز العدد الأعظمي المُتاح.

أمّا الآن، فسنضيف الصنف التالي بعد تعليمة الاستيراد import مُباشرةً على النحو التالي:

class CourseForm(FlaskForm):
    title = StringField('Title', validators=[InputRequired(),
                                             Length(min=10, max=100)])
    description = TextAreaField('Course Description',
                                validators=[InputRequired(),
                                            Length(max=200)])
    price = IntegerField('Price', validators=[InputRequired()])
    level = RadioField('Level',
                       choices=['Beginner', 'Intermediate', 'Advanced'],
                       validators=[InputRequired()])
    available = BooleanField('Available', default='checked')

نحفظ الملف ونغلقه.

ورثنا في الصنف CourseForm أعلاه الشيفرات من الصنف الأساسي FlaskForm المُستورد أصلًا من حزمة فلاسك، كما عرّفنا مجموعةً من حقول النموذج مثل متغيرات لهذا الصنف باستخدام حقول النموذج نفسها المستوردة سابقًا من المكتبة WTForms، بحيث يكون عنوان الحقل هو الوسيط الأوّل لدى استنساخه.

نعرّف المُدققين اللازمين لكل حقل بتمرير قائمة بأسماء المُدققين المُستوردين أصلًا من الوحدة wtforms.validators مثل وسيط لمتغير الصنف، فمثًلا بالنسبة للصندوق النصّي المُخصّص لعنوان الدورة التدريبية "title" في مثالنا، يكون وسيط العنوان فيه هو السلسلة النصية Title، مع تمرير مُدققين، هما:

  • InputRequired: للدلالة على كون هذا الحقل مطلوب ملؤه من قبل المُستخدم ولا يجوز تركه فارغًا.
  • Length: ويُمرّر له وسيطين، الأوّل هو min الذي يدل على الحد الأدنى المطلوب من عدد المحارف للسلسلة وهو في مثالنا "10"، أي لا ينبغي لطول سلسلة العنوان أن يقل عن عشرة محارف، أمّا الوسيط الثاني max فيشير إلى الحد الأعظمي المسموح فيه لعدد محارف السلسلة، وهو في حالتنا "100"، وبالتالي لا يجوز لطول العنوان المُدخل في الصندوق النصي أن يتجاوز مئة محرف.

يتضمن الحقل النصي مُتعدّد الأسطر المُخصّص لوصف description الدورة التدريبية مُصادقين أيضًا، هما: InputRequired الذي يعني أنّ هذا الحقل مطلوب، والآخر Length جاعلًا قيمة الوسيط max تساوي "200"، دون وجود قيمة لوسيط الحد الأدنى min، ما يعني أنّ المطلوب فقط عدم تجاوز طول السلسلة المُدخلة لمئتي محرف.

نعرّف بنفس الطريقة حقلًا مطلوبًا لإدخال قيمة عددية صحيحة تُمثّل تكلفة الدورة التدريبية باسم price؛ أما الحقل المُخصّص لمستوى الدورة level فهو زر انتقاء ذو عدّة خيارات، بحيث نعرّف هذه الخيارات ضمن قائمة بايثون ومن ثمّ نمررها قيمةً للوسيط choices من زر الانتقاء، ويُعرَّف هذا الحقل أيضًا على أنه مطلوب ملؤه باستخدام المُدقق InputRequired.

أمّا عن الحقل available (وهو حقل مربع اختيار check box field) فهو يدل على أن الدورة مُتاحةٌ للتسجيل حاليًا، وقد عيّنا القيمة الافتراضية له ليكون مُفعلًا checked أي جرى اختياره عبر تمرير هذه القيمة إلى معامل الحالة الافتراضية default، ما يعني أنّ مُربّع الاختيار هذا سيكون مُفعّلًا افتراضيًا بمجرّد إضافة أي دورة جديدة للدلالة على أنها مُتاحة، إلّا في حال ألغى المُستخدم تفعيله يدويًا.

ومع نهاية هذه الخطوة نكون قد أعددنا نموذج الويب المطلوب ضمن الملف المُسمّى "forms.py"، وسنعمل في الخطوة التالية على إنشاء تطبيق فلاسك، وسنستورد فيه هذا النموذج ونعرض حقوله ضمن صفحة التطبيق الرئيسية، كما سننشئ صفحةً أُخرى لعرض قائمة بالدورات التدريبية.

يمكنك الاطلاع على صفحة دورة Crash في توثيق WTForms لمزيدٍ من المعلومات حول كيفية استخدام مكتبة WTForms، ومراجعة صفحة الحقول والمدققين للتأكد من صحة بيانات النموذج.

الخطوة الثالثة - عرض نموذج الويب وقائمة الدورات التدريبية

سنعمل في هذه الخطوة على إنشاء تطبيق فلاسك، بحيث سنعرض في صفحته الرئيسية نموذج الويب المُعدّ في الخطوة السابقة، كما سننشئ قائمةً لتتضمّن الدورات التدريبية مع صفحة مُخصّصة لعرض هذه القائمة.

لذا، وبعد التأكّد من تفعيل البيئة البرمجية وتثبيت فلاسك، سننشئ ملفًا باسم "app.py" ضمن المجلد flask_app لنحرّره:

(env)user@localhost:$ nano app.py

سنستورد في هذا الملف الصنف والمُساعِدات اللازمة من فلاسك، كما سنستورد النموذج CourseForm من الملف "forms.py"، كما سنبني قائمةً بالدورات التدريبية، ومن ثمّ سنستنسخ instantiate النموذج ونمرّره إلى ملف ليكون قالبًا، ولإنجاز ذلك نكتب الشيفرات التالية ضمن الملف app.py:

from flask import Flask, render_template, redirect, url_for
from forms import CourseForm

app = Flask(__name__)
app.config['SECRET_KEY'] = 'your secret key'


courses_list = [{
    'title': 'Python 101',
    'description': 'Learn Python basics',
    'price': 34,
    'available': True,
    'level': 'Beginner'
    }]


@app.route('/', methods=('GET', 'POST'))
def index():
    form = CourseForm()
    return render_template('index.html', form=form)

نحفظ الملف ونغلقه.

استوردنا في الشيفرة السابقة من فلاسك ما يلي:

  • الصنف Flask اللازم لإنشاء نسخة من تطبيق فلاسك.
  • الدالة ()render_template اللازمة لتصيّير قالب الصفحة الرئيسية.
  • الدالة ()redirect اللازمة لإعادة توجيه المُستخدم إلى الصفحة المُخصّصة بعرض الدورات التدريبية بمجرّد إضافة دورة جديدة.
  • الدالة ()url_for اللازمة لبناء الروابط.

استوردنا بدايةً الصنف ()CourseForm من الملف "forms.py"، ثمّ انشأنا النسخة الفعلية من تطبيق فلاسك باسم "app"؛ كما أعددنا الضبط اللازم لمفتاح الأمان الخاص بنماذج WTForms لاستخدامه لدى توليد المفتاح المساعد للحماية من هجمات CSRF، وهذا سيساعد بالنتيجة على تأمين نماذج الويب، مع الأخذ بالحسبان أنّ مفتاح الأمان يجب أن يكون سلسلةً نصيةً عشوائية بطول مناسب.

أنشأنا بعد ذلك قائمةً من قواميس بايثون باسم courses_list، ولا تحتوي هذه المرحلة سوى على قاموس واحد يتضمّن دورة تجريبية بعنوان Python 101، بمعنى أنّنا نستخدم قائمة بايثون لتخزين البيانات وذلك كون مقالنا تعليمي ولسنا بصدد شرح طرق التخزين فيه، ولكن في التطبيقات العملية الواقعية نستخدم قاعدة بيانات لتُخزّن هذه البيانات بصورةٍ دائمة سامحةً لنا بتعديلها واسترجاعها بسهولة وفعالية، وللتعرّف على كيفية استخدام قاعدة بيانات لتخزين بيانات الدورات ننصحك بقراءة المقال [How To Use an SQLite Database in a Flask Application](أحد مقالات المجموعة الحالية 531039).

أنشأنا الوجهة الرئيسية / باستخدام المزخرف ()app.route ضمن دالة العرض ()index، إذ تتعامل هذه الوجهة مع كلا نوعي طلبات HTTP وهما GET و POST من خلال المعامل methods، إذ تتخصّص الطلبات من النوع GET بجلب البيانات، أما الطلبات من النوع POST فهي مُتخصّصة بإرسال البيانات إلى الخادم عبر نموذج ويب مثلًا.

بعد ذلك، استنسخنا الصنف ()CourseForm المُمثّل لنموذج الويب، وحفظنا هذه النسخة ضمن متغير باسم form، لتكون القيمة المعادة هي استدعاء للدالة ()render_template ممرين لها ملف قالب باسم "index.html" مع نسخة النموذج.

بغية عرض نموذج الويب ضمن الصفحة الرئيسية للتطبيق، سننشئ بدايةً ملف قالب ليتضمّن كافة شيفرات HTML الأساسية اللازمة لترثها لاحقًا القوالب الأُخرى، وسيجنّبنا هذا تكرار الشيفرات، ومن ثمّ سننشئ ملف قالب الصفحة الرئيسية "index.html" المُصيّر أصلًا باستخدام الدالة ()index.

الآن، سننشئ مجلدًا للقوالب باسم "templates"، إذ سيبحث فلاسك عن القوالب ضمن المجلد "flask_app"، وسننشئ ضمن مجلّد القوالب هذا ملف قالب باسم "base.html"، الذي سيمثّل القالب الأساسي لبقية القوالب على النحو التالي:

(env)user@localhost:$ mkdir templates
(env)user@localhost:$ nano templates/base.html

وسنكتب فيه الشيفرات التالية لإنشاء القالب الرئيسي بحيث يتضمّن شريط تصفُّح وكتلة محتوى:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>{% block title %} {% endblock %} - FlaskApp</title>
    <style>
        nav a {
            color: #d64161;
            font-size: 3em;
            margin-left: 50px;
            text-decoration: none;
        }
    </style>
</head>
<body>
    <nav>
        <a href="{{ url_for('index') }}">FlaskApp</a>
        <a href="#">About</a>
    </nav>
    <hr>
    <div class="content">
        {% block content %} {% endblock %}
    </div>
</body>
</html>

إذ يتضمّن القالب الأساسي كافّة الشيفرات المتداولة التي ستحتاجها في القوالب الأُخرى.

ستُستبدل لاحقًا كتلة العنوان title بعنوان كل صفحة، وكتلة المحتوى content بمحتواها، أمّا عن شريط التصفح فسيتضمّن رابطين، الأوّل ينقل المُستخدم إلى الصفحة الرئيسية للتطبيق باستخدام الدالة المساعدة ()url_for لتحقيق الربط مع دالة العرض ()index، والآخر لصفحة المعلومات حول التطبيق في حال قررت تضمينها في تطبيقك.

نحفظ الملف ونغلقه.

الآن، سننشئ ملف قالب باسم "index.html" وهو الاسم الذي حددناه في الملف "app.py":

(env)user@localhost:$ nano templates/index.html

سيتضمّن هذا الملف نموذج الويب المُمرر إلى القالب "index.html" عن طريق المتغير form، وسنضيف ضمنه الشيفرات التالية:

{% extends 'base.html' %}

{% block content %}
    <h1>{% block title %} Add a New Course {% endblock %}</h1>

    <form method="POST" action="/">
        {{ form.csrf_token }}
        <p>
            {{ form.title.label }}
            {{ form.title(size=20) }}
        </p>

        {% if form.title.errors %}
            <ul class="errors">
                {% for error in form.title.errors %}
                    <li>{{ error }}</li>
                {% endfor %}
            </ul>
        {% endif %}

        <p>
            {{ form.description.label }}
        </p>
        {{ form.description(rows=10, cols=50) }}

        {% if form.description.errors %}
            <ul class="errors">
                {% for error in form.description.errors %}
                    <li>{{ error }}</li>
                {% endfor %}
            </ul>
        {% endif %}

        <p>
            {{ form.price.label }}
            {{ form.price() }}
        </p>

        {% if form.price.errors %}
            <ul class="errors">
                {% for error in form.price.errors %}
                    <li>{{ error }}</li>
                {% endfor %}
            </ul>
        {% endif %}

        <p>
            {{ form.available() }} {{ form.available.label }}
        </p>

        {% if form.available.errors %}
            <ul class="errors">
                {% for error in form.available.errors %}
                    <li>{{ error }}</li>
                {% endfor %}
            </ul>
        {% endif %}

        <p>
            {{ form.level.label }}
            {{ form.level() }}
        </p>

        {% if form.level.errors %}
            <ul class="errors">
                {% for error in form.level.errors %}
                    <li>{{ error }}</li>
                {% endfor %}
            </ul>
        {% endif %}

        <p>
            <input type="submit" value="Add">
        </p>
    </form>

{% endblock %}

نحفظ الملف ونغلقه.

امتد القالب الرئيسي في بداية الشيفرة، وعيّنا عنوانًا للصفحة ضمن تنسيق عنوان من المستوى الأوّل باستخدام الوسم <h1>، ثمّ صيّرنا حقول نموذج الويب ضمن الوسم <form>، وضبطنا نوع طلبات HTTP الخاصّة به لتكون من النوع POST، والحدث المرتبط به ليكون الانتقال إلى الوجهة الرئيسية / المُتمثّلة بالصفحة الرئيسية للتطبيق. صيّرنا بدايةً المفتاح المساعد token الذي تستخدمه نماذج WTForms لحماية النموذج من هجمات CSRF باستخدام التعليمة {{ form.csrf_token }}، إذ يُرسل هذا المفتاح إلى الخادم مع بقية بيانات النموذج، ومن المهم جدًا تصيّير المفتاح المساعد لجعل النماذج آمنة.

صيّرنا كل حقل من النموذج باستخدام الصيغة ()form.field، كما صيّرنا عنوان كل منها باستخدام الصيغة form.field.label، وهنا من الممكن تمرير وسطاء إلى كل حقل والتي من شأنها التحكّم بطريقة عرضه، فمثلًا عيّنا حجم الصندوق النصي الخاص بالعنوان بالشّكل {{ form.title(size=20) }}، كما عيّنا عدد الأسطر والأعمدة ضمن الحقل النصي مُتعدّد الأسطر والخاص بوصف الدورة بالشّكل من خلال المعاملين rows و cols وبنفس الطريقة التقليدية المُتبعة في لغة HTML، وبذلك من الممكن إضافة ما نشاء من سمات HTML إلى الحقول، مثل سمة class مثلًا بغية تعيّين صنفٍ لشيفرات CSS.

استخدمنا الصيغة if form.field.errors للتحقّق من وجود أخطاء في المُصادقة، ففي حال وجود أخطاء في حقلٍ ما، سيجري المرور عليها باستخدام حلقة for تكرارية لتُعرض ضمن قائمة أسفل الحقل.

الآن، ومع وجودنا ضمن المجلد "flask_app" ومع كون البيئة الافتراضية مُفعّلة، سنُعلم فلاسك بالتطبيق المراد تشغيله (وهو في حالتنا الملف app.py) باستخدام متغير البيئة FLASK_APP، ثم نضبط متغير البيئة FLASK_ENV لتشغيل التطبيق في وضع التطوير development، مع إمكانية الوصول إلى مُنقّح الأخطاء؛ ولمزيدٍ من المعلومات حول مُنقّح الأخطاء في فلاسك ننصحك بقراءة المقال كيفية التعامل مع الأخطاء في تطبيقات فلاسك، ولتنفيذ ما سبق سنشغّل الأوامر التالية (مع ملاحظة أنّنا نستخدم الأمر set في بيئة ويندوز عوضًا عن الأمر export?

(env)user@localhost:$ export FLASK_APP=app
(env)user@localhost:$ export FLASK_ENV=development

والآن سنشغّل التطبيق باستخدام الأمر التالي:

(env)user@localhost:$ flask run

وبعد التأكد من كون خادم التطوير ما يزال قيد التشغيل، نذهب إلى الرابط التالي باستخدام المتصفح:

http://127.0.0.1:5000/

فيظهر نموذج الويب ضمن الصفحة الرئيسية للتطبيق كما هو موضح في الشكل التالي:

شكل نموذج الويب ضمن الصفحة الرئيسية للتطبيق

ولو جرّبنا إرسال النموذج مع ترك حقل العنوان فارغًا، ستظهر رسالة خطأ تعلمنا بأن العنوان مطلوب، وهنا من الجيد تجربة إرسال نماذج ببيانات لا تحقق الشروط بغية التعرّف على رسائل الخطأ المُختلفة، مثل إدخال عنوان بأقل من 10 محارف، أو وصف يتجاوز 200 محرف، أمّا إذا ملأنا النموذج ببيانات صحيحة وأرسلناه فلن يحدث شيء حاليًا، والسبب أنّنا لم نضف حتى الآن الشيفرة اللازمة للتعامل مع النماذج المُرسلة، الأمر الذي سنفعله لاحقًا.

سنعمل الآن على إضافة صفحة لعرض الدورات الموجودة في القائمة، وسنضيف فيما بعد ما يلزم للتعامل مع البيانات الآتية من نموذج الويب، والتي ستضيف بالنتيجة دورة جديدة إلى القائمة، ليُعاد بعدها توجيه المُستخدم إلى صفحة عرض الدورات ليرى أنّ الدورة الجديدة قد أُضيفت فعلًا.

لذلك، سنفتح نافذة أسطر أوامر جديدة مع بقاء خادم التطوير قيد التشغيل، ثمّ سنفتح الملف app.py لإضافة الوجهة الخاصّة بصفحة عرض الصفحات إليه:

(env)user@localhost:$ nano app.py

وسنضيف الوجهة التالية إلى نهاية الملف:

# ...

@app.route('/courses/')
def courses():
    return render_template('courses.html', courses_list=courses_list)

نحفظ الملف ونغلقه.

تُخرج الوجهة السابقة قالبًا باسم "courses.html" ممرّرة له قائمة الدورات courses_list.

أمّا الآن فسننشئ القالب "courses.html" المسؤول عن عرض الدورات:

(env)user@localhost:$ nano templates/courses.html

ونكتب ضمنه الشيفرة التالية:

{% extends 'base.html' %}

{% block content %}
    <h1>{% block title %} Courses {% endblock %}</h1>
    <hr>
    {% for course in courses_list %}
        <h2> {{ course['title'] }} </h2>
        <h4> {{ course['description'] }} </h4>
        <p> {{ course['price'] }}$ </p>
        <p><i>({{ course['level'] }})</i></p>
        <p>Availability:
            {% if course['available'] %}
                Available
            {% else %}
                Not Available
            {% endif %}</p>
        <hr>
    {% endfor %}
{% endblock %}

نحفظ الملف ونغلقه.

عيّنا في الشيفرة السابقة عنوانًا، ثمّ مررنا على كافّة عناصر القائمة courses_list، إذ سيُعرض العنوان ضمن تنسيق عنوان من المستوى الثاني باستخدام الوسم <h2>، والوصف ضمن تنسيق عنوان من المستوى الرابع <h4>، أمّا كل من السعر والمستوى فسيُعرضان ضمن تنسيق فقرة باستخدام الوسم <p>، ويجري التحقّق من كون الدورة مُتاحة باستخدام العبارة الشرطية ['if course['available، لتُعرض العبارة "Available"، والتي تعني أن الدورة مُتاحة في حال توفرها، والعبارة "Not Available" في حال عدم توفرها.

وبالانتقال إلى صفحة الدورات باستخدام الرابط التالي في المتصفح:

http://127.0.0.1:5000/courses/

ستظهر الصفحة مُتضمّنةً دورةً تدريبيةً واحدةً وهي تلك التي أضفناها يدويًا إلى قائمة بايثون سابقًا، كما هو موضح في الشّكل التالي:

ظهور الصفحة متضمنة دورة تدريبية واحدة

أمّا الآن، سنفتح ملف القالب الأساسي "base.html" لتعديله بغية إضافة رابط مباشر ينقلنا إلى صفحة عرض الدورات وذلك ضمن شريط التصفّح:

(env)user@localhost:$ nano templates/base.html

ونعدله ليبدو على النحو التالي:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>{% block title %} {% endblock %} - FlaskApp</title>
    <style>
        nav a {
            color: #d64161;
            font-size: 3em;
            margin-left: 50px;
            text-decoration: none;
        }
    </style>
</head>
<body>
    <nav>
        <a href="{{ url_for('index') }}">FlaskApp</a>
        <a href="{{ url_for('courses') }}">Courses</a>
        <a href="#">About</a>
    </nav>
    <hr>
    <div class="content">
        {% block content %} {% endblock %}
    </div>
</body>
</html>

نحفظ الملف ونغلقه.

الآن سيظهر رابط الانتقال إلى صفحة عرض الدورات ضمن شريط التصفّح عند تحديث الصفحة الرئيسية للتطبيق في المتصفّح.

ومع نهاية هذه الخطوة نكون قد أنشأنا الصفحات اللازمة للتطبيق، وهي: الصفحة الرئيسية "index.html" المُتضمّنة نموذج ويب لإضافة دورات جديدة، وصفحة لعرض الدورات المُخزّنة ضمن قائمة الدورات "courses.html".

لجعل التطبيق يعمل فعليًا، سنعالج في الخطوة التالية البيانات التي يرسلها المُستخدم عبر نموذج الويب من حيث التحقّق من صحتها وإضافتها إلى قائمة الدورات.

الخطوة الرابعة - الوصول إلى بيانات النموذج

سنعمل في هذه الخطوة على الوصول إلى البيانات التي أرسلها المُستخدم عبر النموذج للتأكّد من صحتها ومن ثمّ إضافتها إلى قائمة الدورات. لذلك، سنفتح الملف "app.py" لتعديله بإضافة الشيفرات اللازمة للتعامل مع بيانات نموذج الويب وذلك ضمن الدالة ()index:

(env)user@localhost:$ nano app.py

وسنعدّل الدالة ()index لتصبح كما يلي:

# ...
@app.route('/', methods=('GET', 'POST'))
def index():
    form = CourseForm()
    if form.validate_on_submit():
        courses_list.append({'title': form.title.data,
                             'description': form.description.data,
                             'price': form.price.data,
                             'available': form.available.data,
                             'level': form.level.data
                             })
        return redirect(url_for('courses'))
    return render_template('index.html', form=form)

نحفظ الملف ونغلقه.

استدعينا في الشيفرة السابقة التابع ()validate_on_submit من الكائن form، إذ يتحقّق هذا التابع من كون طلب HTTP من النوع POST، ثمّ شغلنا المدققات التي أعددناها سابقًا لكل حقل من الحقول، فإذا أعاد أي منها خطأ ما لن يتحقق الشرط أي ستكون قيمته False، وسيظهر كل خطأ أسفل الحقل الذي تسبب بحدوثه.

أمّا في حال كانت كافّة البيانات المُدخلة صحيحة، سيتحقق الشرط أي ستكون قيمته True وبالتالي سيُنفّذ جزء الشيفرة التالي لتعليمة if، وفيها نبني قاموس بايثون للدورة الجديدة ومن ثمّ نستخدم التابع append لإضافة هذه الدورة إلى قائمة الدورات courses_list، إذ استخدمنا الصيغة form.field.data للوصول إلى قيمة كل حقل في النموذج، ونهايةً وبعد إضافة الدورة الجديدة إلى قائمة الدورات، نعيد توجيه المُستخدم إلى صفحة عرض الدورات.

الآن، سننتقل إلى الصفحة الرئيسية للتطبيق مع كون خادم التطوير قيد التشغيل، باستخدام الرابط:

http://127.0.0.1:5000/

وبملء النموذج ببيانات صحيحة وإرساله، نلاحظ إضافة الدورة الجديدة فعلًا، وسنُوجّه تلقائيًا إلى صفحة عرض الدورات من التطبيق والتي ستظهر فيها الدورة الجديدة المُضافة.

الخلاصة

أنشأنا في هذه المقال تطبيق فلاسك ذا نموذج ويب مبني باستخدام الإضافة Flask-WTF والمكتبة WTForms، إذ ضمّنا في نموذج الويب هذا العديد من أنواع الحقول التي من شأنها استقبال البيانات من المُستخدم وتحقّقنا من صحتها بالاعتماد على المُدققين الخاصين بمكتبة WTForms، لنضيف هذه البيانات بالنتيجة إلى مخزن البيانات وهو قائمة بايثون في مثالنا.

ترجمة -وبتصرف- للمقال How To Use and Validate Web Forms with Flask-WTF لصاحبه Abdelhadi Dyouri.

اقرأ أيضًا


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...