flask_wtforms عرض النّماذج باستخدام Jinja والوصول إلى بيانات نماذج WTForms في تطبيقات Flask


عبدالهادي الديوري

مُقدّمة

تعرّفنا في الدّرسين السّابقين على بعض المفاهيم المُتعلّقة بإدارة نماذج HTML والتّحقق من مُدخلات المُستخدم باستخدام مكتبة WTForms، إذ تعرّفنا على كيفيّة إنشاء صنف يُمثّل نموذج HTML وعلى كيفيّة تقديمه إلى قالب باسم form.html، وقد حان الوقت لنستغلّ إمكانيّات مُحرّك القوالب Jinja لعرض النّموذج والحقول التّي فيه بملفّ HTML وبالتّالي فسيتمكّن المُستخدم من ملء النّموذج من مُتصّفحه وإرسال البيانات إلى الخادوم (تطبيق Flask الخاص بنا)، لنقوم بعد ذلك بالوصول إلى المُدخلات ومُعالجتها.

flask-wtf-03.png

أساسيّات عرض النّموذج

ليتمكّن المُستخدم من إرسال البيانات إلى التّطبيق لنُعالجها لا بد من تقديم نموذج HTML الذي أنشأناه ليصل إليه المُستخدم عن طريق مُتصفّحه، وبما أنّنا قدّمنا الكائن الذي يحمل النّموذج إلى ملف باسم form.html فهذا يعني بأنّنا سنستطيع الوصول إليه من هذا الملف.

تذكير: عند التّعامل مع إطار العمل فلاسك فإنّ ملفّات HTML تكون داخل مُجلّد باسم templates.

سيكون مُحتوى ملفّ HTML بسيطا، وأهم شيء هو تقديم الحقول التّي سبق وأن حدّدناها في الصّنف LoginForm أي الحقلين username و password وسنتمكّن من الوصول إليهما كما يلي:

form.username

form.password

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

<input id="username" name="username" type="text" value="">


<input id="password" name="password" type="password" value="">

لو تمعّنت في الشّيفرة أعلاه، ستجد بأنّها حقول HTML عاديّة، وهذا يُعطينا فكرة لكيفيّة استغلالها لعرض الحقول على قالب HTML باستخدام مُحرّك القوالب Jinja.
ما يعني بأنّنا سنتمكّن من عرض كل حقل على حدة كما يلي:

{{ form.username }}

{{ form.password }}

أمّا لصيقة كل حقل فسنتمكّن من الوصول إليها كما يلي:

form.username.label

form.password.label

والتّالي مُحتويات ما سبق:

<label for="username">Username</label>

<label for="password">Password</label>

مُجدّدا، لغة HTML عاديّة، وبالتّالي سنتمكّن كذلك من عرض كل حقل واسمه بجانبه على ملف القالب كما يلي:

{{ form.username.label }} {{ form.username }}

{{ form.password.label }} {{ form.password }}

عرض الأخطاء التّي يُنتجها المُصادقون

عندما عرّفنا الصّنف وحقلي اسم المُستخدم وكلمة المرور، فقد وضعنا المُصادق DataRequired لكل حقل لجعله مطلوبا (أي لا يمكن أن يكون فارغا)، ونحن نعلم بأنّ خطأ سيحدث إن حاول المُستخدم إرسال بيانات دون ملء الحقول، لذا لن تمرّ البيانات إلى الخادوم وبالتّالي فلن يحدث شيء ولن يفهم المُستخدم ما الذي حدث، الأمر بسيط حاليا، لكن ماذا لو كان لدينا حقل بعدّة مُصادقين، مثلا لو كان الحقل ذو مُصادق يطالب بكون البيانات المُدخلة بين 6 و 20 حرفا وفي نفس الوقت يجب على هذه الأحرف أن تحتوي على أرقام أو رموز أخرى، والمُستخدم أدخل خمسة أحرف فقط أو أنّه أدخل عشرة أحرف دون أية أرقام أو رموز، وبما أنّ الخطأ لا يُعرض فمن الصّعب على المُستخدم أن يفهم ما الخطب ليُصلحه، لذا فمكتبة WTForms توفّر لنا طريقة بسيطة لعرض الخطأ الذي ارتكبه المُستخدم له ليتفاداه.

حسب ما سبق، فالوصول إلى الحقل يكون كالآتي:

form.field

مع استبدال field باسم الحقل بالطّبع، مثل form.password أو form.username.

أمّا للوصول إلى الأخطاء التّي يُنتجها المُصادق، فتستطيع إلحاق errors بالحقل، ويُمكن أن تكون هناك أخطاء بالفعل (إن كان المُصادق يُصدر خطأ) كما يُمكن أن تكون قائمة الأخطاء فارغة، وذلك حسب البيانات التّي يُرسلها المستخدم، وهذا واضح جدّا، إن كانت هناك أخطاء عند المُصادقة فالقائمة التّاليّة ستحتوي عليها وسيتوجّب عليك الدّوران حولها لعرضها الواحدة بعد الأخرى:

form.field.errors

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

if form.field.errors:
    for error in form.field.errors:
        return error

بعد أن فهمت الطّريقة، إليك كيفيّة استعمالها على صفحة HTML بالاعتماد على مُحرّك القوالب Jinja، والمثال التّالي يُعطيك فكرة على كيفيّة استغلال هذا الأمر لعرض الأخطاء التّي سيُنتجها المُصادق على الحقل username:

{% if form.username.errors %}
    {% for error in form.username.errors %}
       {{ error }}
    {% endfor %}
{% endif %}

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

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

إضافة خصائص HTML إلى الحقل

عندما تتعامل مع الحقول، بالإضافة إلى الخصائص الافتراضيّة قد ترغب بإضافة خصائص أخرى لكل حقل، فقد ترغب مثلا بتوفير نص بديل Placeholder أو إضافة id أو class ليُساعدك ذلك على تنسيق الحقل أو إضافة ميّزات ديناميكيّة باستخدام جافاسكربت.

يُمكنك القيام بذلك عند تقديم الحقل بتمرير مُعاملات لكل حقل بحيث يكون للمُعامل نفس اسم الخاصيّة وقيمة المُعامل عند تمريره هي قيمة الخاصّية.

form.username(placeholder= 'Enter your username')

النّتيجة ستكون كما يلي:

<input id="username" name="username" placeholder="Enter your username" type="text" value="">

ولو أردت مثلا أن تُضيف الخاصيّة required الجديدة في HTML5 فيُمكنك القيام بالأمر كما يلي:

form.username(placeholder= 'Enter your username', required = True)

والنّتيجة ستكون كما يلي:

<input id="username" name="username" placeholder="Enter your username" required type="text" value="">

وكما تُلاحظ، فالخاصيّة required قد حدّدت كما يجب أن تكون في مصدر HTML، وبالتّالي فسنستطيع التّحقق من أنّ الحقل لا يُرسل فارغا مرّتين (الأولى باستخدام HTML5 والثّانيّة باستخدام WTForms)، وهذا بالضّبط ما كنت أتحدّث عنه عندما نبّهتك إلى عدم التّحقق من البيانات في جهة العميل.

ولو انتبهت جيّدا لمصدر HTML الخاص بالحقل فستجد بأنّ قيمته (ما يكون في الحقل افتراضيا) ستكون فارغة، ويُمكنك بالطّبع أن تُحدّدها كذلك ببساطة:

form.username(placeholder= 'Enter your username', required = True, value = 'Default Value')

النّتيجة:

<input id="username" name="username" placeholder="Enter your username" required type="text" value="Default Value">

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

عرض النّموذج على صفحة HTML باستعمال مُحرّك القوالب Jinja

تذكير: الشّيفرة التّالية تُمثّل الجزء المسؤول عن تمرير الكائن form إلى الملف form.html:

render_template('form.html', form = form)

انطلاقا من الكائن form سنتمكّن من الوصول إلى كل من الحقلين username و password في ملف HTML وسنتمكّن من عرضها وعرض أسمائها والأخطاء التّي يُصدرها كل مُصادق.

بالمثال يتّضح المقال، لذا إليك مُحتوى الملف form.html الذي سيحمل الشّيفرة الأساسيّة لعرض النّموذج، وبالطّبع فقد تجاهلت الوسوم html و head و body وركّزت على الجزء الأهم ليكون المثال أقصر وأكثر وضوحا:

<form method='post'>

    {{ form.hidden_tag() }}

    {{form.username.label}}
    <br>
    {{form.username}}
    {% if form.username.errors %}
           <ul class=errors>
           {% for error in form.username.errors %}
               <li>{{ error }}</li>
           {% endfor %}
           </ul>
    {% endif %}
    <br>
    {{form.password.label}}
    <br>
    {{form.password}}
    {% if form.password.errors %}
           <ul class=errors>
           {% for error in form.password.errors %}
               <li>{{ error }}</li>
           {% endfor %}
           </ul>
    {% endif %}
    <br>
    <input type='submit' value='Login'>
</form>

كل ما فعلناه هو أنّنا جمعنا جميع القطع التّي شرحتها أعلاه مع الفصل بينها بفواصل <br>، إذ نقوم أولا بإنشاء وسم النّموذج <form> مع تحديد نوع الطّلب post ثمّ نقوم بإغلاق الوسم في آخر الملف.

داخل وسمي <form></form>، سنقوم بتحديد الحقل الخفي الذي سيُمكّننا من تفادي هجمات CSRF عن طريق استدعاء التّابع hidden_tag، وبالتّالي ففي كل مرّة يُرسل فيها مُستخدم بياناته فستُرسل البيانات في الحقول مع الحقل الخفي ثمّ يتم التّحقق منه.

بعد تحديد الحقل الخفي، نقوم بعرض لصيقة كل حقل، ثمّ نقوم بعرض الحقل وبعدها نعرض الأخطاء التي تتعلّق به (أي الأخطاء التّي يُنتجها كل مُصادق).

وفي الأخير نضيف زرا ليتمكّن المُستخدم من إرسال البيانات.

وهكذا سيُصبح النّموذج كما في الصّورة التّالية:

001.png

وإن حاولت إرسال النّموذج دون ملئ الحقول فستحصل على خطئ من المصادق DataRequired :

002.png

وإن ملأت حقلا وتركت آخر، فالخطأ يظهر عند الحقل الفارغ فقط:

003.png

يُمكنك كذلك النّظر إلى مصدر الصّفحة إن أردت تفحّص ما تُنتجه مكتبة WTForms من حقول.

وإليك الجزء الخاص بالحقل الخفي الذي يُستعمل للحماية من هجمات CSRF.

<div style="display:none;"><input id="csrf_token" name="csrf_token" type="hidden" value="1476126948##ffc7d39e3fe6820d20f495ec6d0be1772ea6f647"></div>

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

الوصول إلى البيانات التّي يُرسلها المُستخدم

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

في السّابق كنّا نصل إلى ما يُرسله المُستخدم عن طريق القاموس form المُتواجد بالوحدة request التّي نقوم باستيرادها من حزمة إطار العمل Flask، ويحدث هذا إذا وفقط إذا كان نوع الطّلب هو POST وليس الطّلب GET، إذ نضع البيانات في مُتغيّرات كما يلي:

if request.method == 'POST':
    username = request.form['username']
    password = request.form['password']
    pass

ثمّ نقوم بمُعالجة البيانات عوضا عن الكلمة المفتاحيّة pass.

عند الاعتماد على مكتبة WTForms للتعامل مع النّماذج فالأمر مُختلف قليلا، إذ نقوم بالوصول إلى مُحتوى حقل كما يلي:

form.field.data

مع تغيير field باسم الحقل، وعوضا عن الوصول إلى البيانات إذا كان الطّلب من النّوع POST فإنّنا نقوم بذلك إذا أرجع التّابع validate_on_submit القيمة المنطقيّة True.

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

if form.validate_on_submit():
    username = form.username.data
    password = form.password.data
    pass

مع مُلاحظة أنّ التّابع validate_on_submit يُرجع القيمة True إذا كان الطّلب من نوع POST ولم تصدر أية أخطاء من المُصادقين، أي أنّ المُستخدم قد ضغط على زر الإرسال وأنّ الحقول آمنة وأنّ التّحقق منها قد تم بنجاح.

كمثال عن كيفيّة مُعالجة هذه البيانات، سنقوم بالتّحقق من أنّ اسم المُستخدم يطابق الكلمة admin وأن كلمة المرور تُطابق 123456، فإن تحقّق الشّرطان، نُرجع جملة تُفيد بأنّ تسجيل الدّخول قد تم، وإن لم يتحقّق أحد أو كلا الشّرطين فسنُرجع جملة تُفيد بأن البيانات غير صالحة.

وبهذا يُصبح المُوجّه الخاص بنا كما يلي:

@app.route('/', methods=['GET', 'POST'])
def form():
    form = LoginForm()
    if form.validate_on_submit():   
        username = form.username.data
        password = form.password.data
        if username == "admin" and password == "123456":
            return 'Logged in'
        else:
            return 'Invalid'

    return render_template('form.html', form = form)

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

خاتمة:

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





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


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



يجب أن تكون عضوًا لدينا لتتمكّن من التعليق

انشاء حساب جديد

يستغرق التسجيل بضع ثوان فقط


سجّل حسابًا جديدًا

تسجيل الدخول

تملك حسابا مسجّلا بالفعل؟


سجّل دخولك الآن