البحث في الموقع
المحتوى عن 'wtforms'.
-
مُقدّمة بعد أن تعرّفنا على كيفيّة العمل مع مكتبة WTForms في تطبيقات Flask، وتعرّفنا على كيفيّة إنشاء نماذج وحقول مُختلفة وكيفيّة عرضها في مُتصفّح المُستخدم وكيفيّة الوصول إلى البيانات التّي يُرسلها المُستخدم، فقد حان الوقت لنتحقق من هذه البيانات باستخدام المكتبة، لنتأكّد من أنّها تُطابق صيغًا وقولعد يُمكننا تحديدها عبر استخدام ميّزة المُصادقين في WTForms. المُصادقون Validators ميّزة من ميّزات مكتبة WTForms تُتيح لنا إمكانيّة التّحقق من مُدخلات المُستخدم والمُصادقة عليها حسب قانون مُعيّن (طول النّص، مجال عدد مُعيّن، صيغة بريد إلكتروني …)، وهناك أنواع مختلفة من المُصادقين، وقد تعرّفنا من قبل على المُصادق DataRequired الذي يتحقّق من أنّ المُدخل غير فارغ، ونُضيفه إلى كل حقل مطلوب ملؤه من قبل المُستخدم، ويرجع رسالة خطأ إذا ما لم تتم المُصادقة على المُدخل (في حالة المُصادق DataRequired فإنّ الرّسالة تُعرض إذا أرسل المُستخدم بيانات فارغة عبر النموذج). وقد تعرّفنا كذلك على كيفيّة استخدام المُصادق، وذلك باستيراده من حزمة wtforms.validators ثمّ تمريره كعنصر من قائمة إلى المُعاملvalidators إلى الصّنف المسؤول عن الحقل عند تعريفه في البداية. وإليك تذكيرا بسيطا لكيفيّة استيراد المُصادق وكيفيّة استعماله في حقل نصي بسيط: from wtforms import TextField from wtforms.validators import DataRequired username = TextField('Username', validators=[DataRequired()]) الآن، أي إرسال للنّموذج مع هذا الحقل فارغا سيفشل، وستظهر رسالة خطأ للمُستخدم إذا ما عرضتها في صفحة HTML. مُصادق البريد الإلكتروني Email البريد الإلكتروني مُكوّن أساسي عند تسجيل مُستخدمين جدد في تطبيقات الويب، وبما أنّه يُعتبر وسيلة أساسيّة للتواصل مع المُستخدم لتنبيههم بتغيير في التّطبيق أو مُساعدتهم على استرداد حسابهم في حالة نسيان كلمة المرور أو عند إضافة أي إجراء أمني آخر، لذا فملئ حقل البريد الإلكتروني عند التّسجيل ببريد حقيقي (أو على الأقل سلسلة نصيّة تبدو على شكل بريد إلكتروني) أمر لا بد منه، ورغم أنّك تستطيع التّحقق من أنّ البريد الإلكتروني ملك للمُستخدم حقّا عبر إرسال رسالة إليه لتأكيده، إلّا أنّ قاعدة البيانات يجب أن لا تحتوي على بيانات عشوائيّة أو غير مُفيدة، لذا فمن المُفضّل التأكّد من أنّ المُدخل يبدو كبريد إلكتروني حقيقي (يعني له بنية عنوان بريد إلكتروني صالح) قبل إدخاله إلى قاعدة البيانات. للتحقق من أنّ المُدخل على شكل بريد إلكتروني، فإنّ مكتبة WTForms تُوفّر لنا مُصادقا (Validator) باسم Email لتمريره كعنصر من القائمة التّي تُمرّر إلى المُعامل validators عند إنشاء حقل مُعيّن. لاستعمال المُصادق، نقوم أولا باستيراده من حزمة wtforms.validators: from flask_wtf import FlaskForm from wtforms import TextField from wtforms.validators import DataRequired, Email class SubscribeForm(FlaskForm): email = TextField( 'Email', validators=[ Email(), DataRequired()] ) الحقل هنا هو حقل النّص القصير TextField. لاحظ بأنّني أبقيت على المُصادق DataRequired للتّأكد من أنّ المُستخدم لا يُرسل النّموذج مع حقل فارغ، وأضفت المُصادق Email كعنصر آخر من القائمة validators. إذا ما حاولت الآن إرسال النّموذج فارغا أو أدخلت بريدا إلكترونيا بشكل غير صحيح فستحصل على رسالة خطأ. المُصادق EqualTo قد ترغب في بعض الأحيان أن تتحقّق من أنّ بيانات مُدخل تُساوي بيانات حقل آخر، وأشهر تطبيق لهذه الفكرة هي حقل تأكيد كلمة المرور الذي يتواجد في مُعظم تطبيقات الويب، الفكرة أنّ المُستخدم قد يُدخل كلمة مروره عند التّسجيل بشكل خاطئ ويضطر إلى تعقيد عمليّة تسجيله أكثر من اللازم بمُحاولة إعادة استرجاع كلمة المرور وتغييرها، لذا فالحل أن نطلب من المُستخدم إدخال كلمة مروره مرّتين للتأكّد من أنّه لم يكتبها بشكل خاطئ، وبالطّبع فكلمة المرور التّي يُدخلها المُستخدم عند تسجيله يجب أن تتساوى مع المُدخل في حقل “تأكيد كلمة المرور”. دور المُصادق واضح، إن لم تتساوى قيمتا الحقلين فرسالة الخطأ ستظهر. لاستعمال المُصادق يجب عليك أولا استيراده وبعدها تُمرّره كما العادة إلى القائمة validators: from flask_wtf import FlaskForm from wtforms import TextField, PasswordField from wtforms.validators import DataRequired, Email, EqualTo class RegisterForm(FlaskForm): email = TextField( 'Email', validators=[ Email(), DataRequired()] ) password = PasswordField('Password', validators=[DataRequired()]) confirm = PasswordField('Confirm your Password', validators=[DataRequired(), EqualTo('password')]) لاحظ بأنّنا نقوم بتمرير الحقل إلى المُصادق بتمرير اسم المُتغيّر الذي يمثّل الحقل (أي password في هذه الحالة) على شكل سلسلة نصيّة كما يلي: EqualTo('password') هكذا نُعلم المُصادق بأنّ قيمة هذا الحقل يجب أن تُساوي قيمة الحقل password. بعد إضافتك لهذا المُصادق، يُمكنك التّحقق من أنّه يعمل بإدخال كلمة مرور في الحقل password وإدخال كلمة مُغايرة في الحقل confirm، ستُلاحظ بأنّ المُصادق يُصدر رسالة خطأ إلى أن تقوم بتوفير كلمة المرور نفسها في كلا الحقلين. يُمكنك استعمال هذا المُصادق في حالات أخرى غير التّحقق من كلمة المرور، فبعض المواقع مثلا تطلب من المُستخدمين الجدد توفير بريدهم الإلكتروني مرّتين عند التّسجيل لتفادي أي خطأ مُحتمل، وأعتقد شخصيا بأنّ طلب توفير كلمة المرور مرّتين يفي بالغرض وبالطّبع فقد تحتاج إلى استعمال هذا المُصادق في تطبيقك لأسباب أخرى. المُصادق IPAddress في الغالب لن تحتاج إلى استخدام هذا المُصادق إلّا إذا كان تطبيقك يتعامل مع الشّبكات أو الخوادم الوهميّة أو ما شابه ذلك، لكنّك ستستفيد بالتّأكيد من معرفة كيفيّة استعماله. المُصادق IPAddress يقوم بالتأكد من أنّ المُدخل عبارة عن عنوان IP صالح، الشيء الذي يحمي من تخريب مُحتمل لقاعدة بياناتك، وتوفّر مكتبة WTForms إمكانيّة التّحقق من أنّ العنوان الذي يُوفّره المُستخدم يبدو على شكل عنوان IP في نُسخته الرّابعة وتستطيع كذلك تخصيص المُصادق ليقبل كذلك النّسخة السّادسة IPv6. تذكّر فقط بأنّك ستحتاج إلى استيراد المُصادق قبل أن تتمكّن من استعماله للتّحقق من المُدخلات: from wtforms.validators import IPAddress يُستعمل المُعامل المُصادق على حقل نص قصير عادي كما يلي: ip_address = TextField('IP address', validators=[DataRequired(), IPAddress()]) هكذا لن تسمح مكتبة WTForms بمرور البيانات إلى حين توفير عنوان IP صالح. عند استعمالك للمُصادق دون تمرير أي مُعامل، فسيتأكّد من أنّ المُدخل مُتوافق مع النّسخة الرّابعة لعناوين IP فقط وإن أردت أن تُقبل عناوين IPv6 كذلك فيُمكنك ذلك عبر تمرير القيمة True إلى المُعامل ipv6 كما يلي: ip_address = TextField('Ip address', validators=[DataRequired(), IPAddress(ipv6=True)]) المُصادق Length إنّ من أهم الأسباب التّي قد تدفعك لاستخدام مكتبة للتّحقق من مُدخلات المُستخدم هي تفادي تخريب قاعدة بيانات التّطبيق ببيانات غير ضروريّة أو بيانات “تخريبيّة”، إذ لا يُمكن الثّقة بالمُستخدمين لأنّهم قد يرتكبون أخطاء عفويّة أو أنّ تطبيقك قد يتعرّض لهجمات بغرض التّخريب، وأهم طريقة لمنع المُستخدمين من استهلاك مساحة كبيرة في قاعدة البيانات هي بتحديد طول كل مُدخل، فاسم المُستخدم مثلا لا يجب أن يزيد عن عشرين حرفا، واسم قسم في الموقع لا يجب أن يكون أطول من اللازم وإلّا فسيُشوّه مظهر قائمة التّصفّح. بالإضافة إلى تقليص طول بعض المُدخلات، فقد تحتاج في بعض الأحيان إلى عدم السّماح بمُدخلات قصيرة، فكلمة المرور مثلا لا يجب أن تكون أقصر من 6 أحرف وإلّا سيسهل اختراق حساب المُستخدم وقد تُفضّل كذلك أن لا يكون اسم المُستخدم أقصر من أربعة إلى خمسة أحرف للحفاظ على تنسيق صفحات الموقع التّي تظهر بها أسماء المُستخدمين. الحل الأمثل لتحديد طول مُحدّد لمُدخل ما هي باستعمال المُصادق Length الذي يُمكّننا عبر تمرير مُعاملات إليه من تحديد حد أدنى وحد أقصى لعدد أحرف كلّ مُدخل. يُمكنك استعمال المُصادق بعد استيراده كما يلي: Length(min=MIN_VALUE, max=MAX_VALUE) استبدل MIN_VALUE بعدد الأحرف الأدنى الذي يُمكن قبوله، واستبدل MAX_VALUE بعدد الأحرف الأقصى، وبالطّبع فإنّك تستطيع توفير قيمة دنيا فقط دون توفير قيمة قصوى، والعكس صحيح كذلك. على سبيل المثال، لنستخدم المُصادق للتأكّد من أنّ اسم المُستخدم سيكون بين 3 إلى 25 حرفا: username = TextField('Username', validators=[DataRequired(), Length(min=3, max=25)] ) هكذا لن تُقبل أية قيمة إن كان طولها أقصر أو أطول ممّا حدّدناه. وكما قُلت سابقا، تستطيع توفير قيمة واحدة فقط، والتّالي مثال على كيفيّة التّحقق من أنّ المُدخل لا يتجاوز 25 حرفا: username = TextField('Username', validators=[DataRequired(), Length(max=25)] ) يُمكنك الآن إدخال أية قيمة ما دام طولها لا يتجاوز القيمة القصوى التّي حدّدناها. وبنفس الطّريقة، تستطيع التّحقق من أنّ المُدخل ليس أقصر من قيمة مُعيّنة: username = TextField('Username', validators=[DataRequired(), Length(min=3)] ) موقع Twitter من أبرز التّطبيقات التّي تعتمد على فكرة تحديد طول المُحتوى ويُمكنك استخدام هذا المُصادق إن أردت تطوير تطبيق مُشابه. المُصادق NumberRange المُصادق Length يعمل مع السّلاسل النّصيّة فقط، ما يعني بأنّك تستطيع تطبيقه على حقل كلمة المرور، حقل النّصوص المُتعدّدة الأسطر Text Area أو أي حقل آخر يقبل قيما نصيّة، أمّا بالنّسبة لحقل الأعداد الصّحيحة IntegerField فهناك مُصادق آخر لتحديد مجال القيم العدديّة (قبول الأعداد الأكبر من 1 والأصغر من 255 على سبيل المثال). لتحديد مجال الأعداد المقبول على حقل الأعداد الصّحيحة فسنستخدم المُصادق NumberRange، وطريقة استخدامه مُشابهة لطريقة استخدام المُصادق Length، إذ توفّر عددا للمُعامل min لتحديد القيمة الدّنيا وتمرّر عددا آخر للمُعامل max لتحديد أكبر عدد يُمكن قبوله. والتّالي مثال بسيط على كيفيّة استيراده وتطبيقه على الحقل IntegerField: from flask_wtf import FlaskForm from wtforms import IntegerField from wtforms.validators import NumberRange class AgeForm(FlaskForm): age = IntegerField('Age', validators=[NumberRange(min=12, max=120)]) المثال واضح، أولا الاستيراد، ثمّ إنشاء صنف مع مُتغيّر ليُمثّل حقل الأعداد الصّحيحة، بعدها نمرّر المُصادق إلى القائمة validators ونمرّر قيمتين للمُعاملين min و max الأول لتحديد العدد 12 كأدنى قيمة والثّاني للتأكد من أنّ العدد المُدخل لا يتجاوز 120. ومثلما هو عليه الحال مع المُصادق Length، فإنّك تستطيع ترك أحد المُعاملين ووضع حد واحد لقيم الحقل، كأن تسمح فقط بالأعداد الموجبة بتحديد العدد 0 كقيمة دنيا، أو أن تتحقّق من أنّ قيمة الحقل لا تتجاوز العدد 1000 بتحديده كقيمة للمُعامل max. ما يلي مثال عن كيفيّة التّحقق من أنّ قيم الحقل أعداد موجبة: NumberRange(min=0) هكذا يُمكن إدخال أي عدد موجب دون حد لأقصى قيمة. وللتّحقق من أنّ المُدخل لا يتجاوز العدد 100: NumberRange(max=100) سيسمح المُصادق الآن بمرور أي عدد ما دام لم يتجاوز المئة، ما يعني بأنّ الأعداد السّالبة مسموح بها كذلك. المُصادق Optional في بعض الأحيان قد ترغب بجعل حقل ما اختياريا، بمعنى أنّ توفير المُدخل غير ضروري ويُمكن أن يُترك الحقل فارغا دون أية مشاكل، في مكتبة WTForms يُمكننا استخدام المُصادق DataRequired للتّحقق من أنّ الحقل لا يُرسل فارغا، أمّا لجعله اختياريا فيُمكننا استخدام المُصادق Optional لتمكين المُستخدم من إرسال النّموذج دون ملء الحقل، مع المُلاحظة إلى أنّ المساحات البيضاء (المسافات) تُعتبر حقلا فارغا كذلك. ما يلي مثال على كيفيّة استخدام المُصادق Optional: from flask_wtf import FlaskForm from wtforms import TextField from wtforms.validators import Optional class RegisterForm(FlaskForm): phone = TextField('Phone Number', validators=[Optional()]) بعد تطبيقك للمُصادق تستطيع ترك الحقل فارغا وإرسال النّموذج دون مشاكل وسيصل إلى الخادوم لتتمكّن من الوصول إلى بيانات الحقول الأخرى. المُصادق AnyOf في بعض الحالات يُمكن أن يكون لديك عمود في قاعدة بيانات التّطبيق يقوم بعمليّة على مجموعة معروفة من البيانات، مثل أسماء الدّول مثلا أو أسماء المدن في بلدك أو مجموعة كلمات محدّدة وقد ترغب بإنشاء حقل لا يقبل سوى هذه القيم، وأي قيمة لا تندرج ضمن المجموعة المقبولة لا يجب أن تمرّ إلى قاعدة البيانات وبالتّالي فمن المُفضّل إيقافها عند التّحقق من المُدخلات. في مكتبة WTForms نستطيع استعمال المُصادق AnyOf للتحقق من أنّ المُدخل يندرج تحت مجموعة محدّدة من القيم وأي قيمة أخرى لا يجب أن تُقبل. لاستخدام المُصادق نقوم أولا باستيراده من حزمة wtforms.validators ثمّ نُطبّقه على الحقل الذي نُريده. from flask_wtf import FlaskForm from wtforms import TextField from wtforms.validators import AnyOf class NameForm(FlaskForm): name = TextField('Name', validators=[AnyOf(['Ahmed', 'Khalid', 'Kamal'])]) بتطبيقنا لهذا المُصادق في المثال أعلاه، فسنتمكّن من التّحقق من أنّ قيمة المُدخل لن تخرج عن مجموعة القيم ‘Ahmed’ و’Khalid’ و’Kamal’، وهكذا سنتأكّد من أنّ قاعدة البيانات لا تحتوي سوى على هذه القيم وبالتّالي فإجراء عمليّات على البيانات الضّخمة مثل إحصائها أو ربطها ببيانات أخرى لن تؤدي إلى أية نتائج سلبيّة. المُصادق NoneOf بعد أن تعرّفنا على كيفيّة استثناء جميع القيم ما عدا مجموعة صغيرة، حان الوقت للتّعرف على كيفيّة التّحقق من أنّ المُدخل لا يوافق قيما مُحدّدة، ما يعني بأنّ جميع القيم ستُقبل عدا إن كانت تندرج تحت مجموعة من القيم نقوم بتحديدها نحن. ولاستثناء مجموعة من القيم فإنّنا نستخدم المُصادق NoneOf، وبما أنّ فكرته مُشابهة لفكرة المُصادق AnyOf فطريقة العمل هي نفسها، بحيث تُمرّر القيم التّي لا يجب أن توافق المُدخل إلى المُصادق كقائمة عند استدعائه، والتّالي مثال على كيفيّة استخدامه لاستثناء القيم ‘Ahmed’ و’Khalid’ و’Kamal’ من مُدخلات الحقل name: from flask_wtf import FlaskForm from wtforms import TextField from wtforms.validators import NoneOf class NameForm(FlaskForm): name = TextField('Country', validators=[NoneOf(['Ahmed', 'Khalid', 'Kamal'])]) بتطبيقك للمُصادِق ستتمكّن من التّحقق من أنّ المُدخل لا يوافق كلّا من الأسماء ‘Ahmed’ و’Khalid’ و’Kamal’ لذا فأي اسم آخر لا يندرج ضمنها سيُقبل وسيصل إلى الخادوم. خاتمة بنهاية هذا الدّرس سنكون قد أتممنا سلسلة الدّروس المُخصّصة لكيفيّة استخدام مكتبة WTForms للتّحقق من مُدخلات المُستخدم، وبما أنّك قد أتممت السّلسلة، فستتمكّن الآن من توفير حماية أكثر لتطبيقك وستُطوّر تطبيقاتك بطريقة أفضل من ذي قبل، وإن كنت تُريد الاستمرار مع إطار العمل Flask لبناء تطبيقات أكبر وأعقد فتستطيع مُتابعة سلسلة إنشاء تطبيق لإدارة المُحتوى باستخدام إطار العمل Flask وإضافاته المُختلفة.
-
مُقدّمة بعد أن تعرّفنا على كيفيّة إنشاء بعض حقول HTML باستخدام مكتبة WTForms في تطبيقات إطار العمل فلاسك مثل حقل مساحة النّص وحقول التّاريخ والأعداد الصّحيحة. عندما نتحدّث عن نماذج HTML فإنّنا لا نعني الحقول التّي تُمكّن المُستخدم من الكتابة داخلها فقط، فهناك حقول ومكوّنات أخرى مثل القوائم المُنسدلة وأزرار الانتقاء Radio button ومربّعات التّأشير Checkbox كذلك. وقد حان الوقت للتعّرف على بقيّة الحقول التّي تُوفّرها المكتبة، وسنتعرّف في هذا الدّرس خصّيصا على كيفيّة إنشاء قوائم مُنسدلة وتقديمها، أزرار الانتقاء ومربّعات التّأشير لمنح المُستخدم طرقًا أسهل لإرسال البيانات إلى الخادوم. إنشاء وتقديم قائمة مُنسدلة سننظر في هذا الجزء إلى كيفيّة إنشاء قائمة مُنسدلة لتوفير حقل اختيار للمُستخدم. لإنشائها، فإنّ القائمة المُنسدلة تحتاج إلى قيمتين، القيمة الأولى هي ما يظهر للمُستخدم والقيمة الثّانيّة هي ما يُرسَل إلى الخادوم، فمثلا يُمكن أن تُوفّر للمُستخدم إمكانيّة اختيار بلد إقامته، وستكون القيمة الأولى عبارة عن الاسم الكامل للبلد، أمّا القيمة الثّانيّة (أي ما سيستقبله إلى الخادوم) فستكون عبارة عن الرّمز المُختصر للبلد لتكون الاختيارات كما يلي: MA => المغرب AU => أستراليا BH => البحرين DZ => الجزائر ولإنشاء القائمة المُنسدلة فإنّنا نقوم باستيراد الصّنف SelectField من مكتبة WTForms ونقوم بإنشاء مُتغيّر من هذا الصّنف كما سبق، الاختلاف هنا هو أنّنا نُمرّر الاختيارات إلى مُعامل باسم choices أثناء تعريف المُتغيّر: في المثال التّالي، نقوم بإنشاء حقل خيارات مُتعدّدة ليختار المُستخدم لغة برمجة مُعيّنة: from wtforms import SelectField class PastebinEntry(Form): language = SelectField( u'Programming Language', choices=[('cpp', 'C++'), ('py', 'Python'), ('rb', 'Ruby')] ) يُمكنك أن تُلاحظ بأنّ ما نُمرّره إلى المُعامل choices عبارة عن قائمة بمجموعات من عنصرين، وكما قلت سابقا، فالعنصر الأول هو ما سيصلك عندما يُرسل المُستخدم البيانات، أمّا العنصر الثّاني فسيظهر للمُستخدم لذا إن اختار المُستخدم الخيار Python فما سيصلك أنت عند جانب الخادوم هو السّلسلة النّصيّة 'py'. بعد إنشاء كائن من هذا الصّنف وتقديمه إلى ملفّ HTML فستتمكّن من تقديم الخيارات في صفحة HTML بنفس الطّريقة التّي كنّا نعرض بها الحقول سابقا: {{form.language.label}} <br> {{form.language}} {% if form.language.errors %} <ul class=errors> {% for error in form.language.errors %} <li>{{ error }}</li> {% endfor %} </ul> {% endif %} ولتفهم سبب توفير قيمتين لكل خيار، فتخيّل أنّ الخيارات طويلة جدا، ولو اخترت مثلا الخيار Python فإنّ قيمة form.language.data ستكون py وبالتّالي ستستطيع مُقارنتها مع قيمة أخرى بسهولة وبشيفرة أقصر. حقل الاختيارات المُتعدّدة تُمكّننا مكتبة WTForms من تقديم حقل اختيارات آخر يسمح للمُستخدم باختيار أكثر من خيار واحد، ويُمكننا إنشاء هذا الحقل عن طريق الصّنف SelectMultipleField والذي يجب عليك استيراده: from wtforms import SelectMultipleField أمّا طريقة توفير الخيارات فهي نفسها الطّريقة المُتّبعة لإنشاء قائمة منسدلة عاديّة، استبدل فقط الصّنف SelectField بالصّنفSelectMultipleField: language = SelectMultipleField( u'Programming Language', choices=[('cpp', 'C++'), ('py', 'Python'), ('rb', 'Ruby')] ) وطريقة تقديم النّموذج هي نفسها، مع إمكانيّة توفير عدد الخيارات بتمريره إلى الخاصيّة size في لغة HTML ليكون الحقل أكثر تناسقا، وطريقة توفير عدد الخيارات هو كما يلي: {{form.language(size=3)}} هكذا ستتمكّن من اختيار أكثر من خيار واحد بجرّ فأرة الحاسوب أو الضّغط بشكل مُستمرّ على مفتاح CTRL من لوحة المفاتيح أثناء الاختيار. عندما تُرسل الخيارات فإنّها تكون داخل قائمة بايثون في form.language.data ، وتستطيع الوصول إليها والتّعامل معها كما تتعامل مع قائمة عاديّة. أزرار الانتقاء أزرار الانتقاء أو Radio buttons تُشبه كثيرا القائمة المُنسدلة، إذ أنّها مُجرّد طريقة أخرى لتوفير خيارات للمُستخدم، إذ تُوفّر سؤالا للمُستخدم وتطلب منه الإجابة باختيار أحد الخيارات، ويُعتبر هذا الحقل مناسبا للأسئلة التّي تقبل جوابًا واحدًا مع توفير خيارات كإجابات مُحتملة (مثل طرح سؤال وتمكين المُستخدم من الإجابة بنعم أو لا عبر اختيار الإجابة المرغوبة). يُمكنك إنشاء حقل لأزرار انتقاء عبر الصّنف RadioField الذي يُمكنك استيراده كذلك من مكتبة WTForms، والتّالي مثال على كيفيّة إنشاء حقل به خياران، Yes و No: radio = SelectField( u'Are you happy?', choices=[('yes', 'Yes'), ('no', 'No')] ) مُجدّدًا، العنصر الأول هو ما سيُرسل إلى الخادوم، أمّا العنصر الثّاني فهو ما يُعرض للمُستخدم. أمّا عرض الأزرار على صفحة HTML فإنّه يكون مُختلفا بعض الشّيء، إذ نقوم هذه المرّة بالدّوران حول كل زر وعرضه على حدة، والتّالي مثال على كيفيّة عرض ما أنشأناه أعلاه: {% for subfield in form.radio %} <tr> <td>{{ subfield }}</td> <td>{{ subfield.label }}</td> </tr> {% endfor %} وكما تُلاحظ، فإنّنا نعرض الزرّ ثمّ اللصيقة Label الخاصّة به. مُربّع التأشير Check Box تُستخدم مُربّعات التّأشير لتمكين المُستخدم من اختيار حقل مُعيّن ليبقى افتراضيّا، مثل مُربّع “تذكّرني” تحت نماذج تسجيل الدّخول، ولأنّه يُعنى بالحصول على المُوافقة أو الرّفض من المُستخدم، فهذا يعني بأنّه يقبل قيمتين منطقيّتين فقط، إمّا أن يؤشّر عليه المُستخدم (أي القيمة المنطقيّة True)، أو يتركه فارغًا ( القيمة المنطقيّة تكون False). لذا ففي WTForms يُمكننا استخدام مُربّع التأشير هذا باستيراد الصّنف BooleanField من المكتبة. تعريف الحقل يكون كما يلي: remember_me = BooleanField('Remember Me') وطريقة العرض على صفحة HTML هي نفسها طريقة عرض الحقل السّلسلة النّصيّة القصيرة TextField: {{form.remember_me}} {{form.remember_me.label}} {% if form.remember_me.errors %} <ul class=errors> {% for error in form.remember_me.errors %} <li>{{ error }}</li> {% endfor %} </ul> {% endif %} لاحظ في أول سطر أعلاه بأنّنا نعرض المربّع أولا ثمّ اللصيقة ليكون الحقل أكثر تناسقًا. عندما تُرسل بيانات النّموذج، فمُحتوى هذا الحقل يكون إمّا القيمة المنطقيّة True إذا ما أشّر المُستخدم على المُربّع، أو القيمة المنطقيّة False إن ترك المُربّع فارغًا. لذا يُمكنك استخدام شرط منطقي في شيفرتك كما يلي: if form.remember_me.data: remember_user() مع افتراض أنّ الدّالة remember_user() تقوم بحفظ بيانات المُستخدم في جلسة طويلة الأمد ليتمكّن من الوصول إلى حسابه حتى ولو أغلق المُتصفّح. إن كان المُستخدم قد أشّر على المُربّع فستكون قيمة form.remember_me.data صحيحة وبالتّالي ستُستدعى الدّالة remember_user() وأمّا إن لم يؤشّر المُستخدم على المُربّع فالقيمة ستكون False وبالتّالي سيتم تجاهل ذلك الجزء من الشّيفرة ولن تُستدعى الدّالة remember_user(). الوصول إلى نوعيّة الحقل قد ترغب بالتّأكد من نوعيّة الحقل الذي تتعامل معه، فمثلًا قد تجد نفسك تتعامل مع الكثير من الحقول، ولكل نوع من هذه الحقول طريقة عرض خاصّة، لذا فمن المُفيد معرفة ماهية الحقل لعرضه بالطّريقة المُلائمة. لمعرفة نوع الحقل تستطيع إلحاق type بعد الحقل كما يلي: form.username.type #=> 'TextField' form.password.type #=> 'PasswordField' form.remember_me.type #=> 'BooleanField' وكما تُلاحظ فالنّوع يكون اسم الصّنف على شكل سلسلة نصيّة، لذا تستطيع كتابة جملة شرطيّة كما يلي: {% if field.type == "BooleanField" %} <div>{{ field }} {{ field.label }}</div> {% endif %} وهذا الأمر مُفيد كثيرًا إذا كنت تستعمل ميّزة الماكرو المُتواجدة في مُحرّك القوالب Jinja، وسنتطرّق إلى الأمر في الفقرة التّاليّة. تفادي تكرار شيفرة عرض الحقول باستخدام الدّوال في مُحرّك القوالب Jinja هل لاحظت بأنّ هناك الكثير من التّكرار في شيفرة عرض الحقول؟ إن لم تلاحظ بعد، فتأمّل الشّيفرة التّاليّة: {% if form.username.errors %} {% for error in form.username.errors %} {{ error }} {% endfor %} {% endif %} {% if form.password.errors %} {% for error in form.password.errors %} {{ error }} {% endfor %} {% endif %} الشّيفرة السّابقة تعرض حقلين فقط، والاختلاف الوحيد بينهما هو أنّ الأول حقل نص قصير، والثّاني حقل كلمة مرور، وسيكون من الجميل إن وضعنا الشّيفرة التّي تتكرّر في دالّة تقبل مُعاملًا يُعبّر عن الحقل. يُوفّر لنا مُحرّك القوالب Jinja ما يُسمّى بالماكرو Macro، ويُشبه كثيرا الدّوال العاديّة في لغة بايثون، والاختلاف الواضح أنّ الماكرو يستعمل بنية جملة مُحرّك القوالب، والتّالي الماكرو الخاص بعرض الحقول: {% macro render_field(field) %} <dt>{{ field.label }} <dd>{{ field(**kwargs)|safe }} {% if field.errors %} <ul class=errors> {% for error in field.errors %} <li>{{ error }}</li> {% endfor %} </ul> {% endif %} </dd> {% endmacro %} الماكرو يبدأ بالكلمة المفتاحيّة macro، ثمّ تُحدّد اسمًا له، وتُحدّد المُعاملات التّي يأخذها (في هذه الحالة يأخذ مُعاملًا واحدا يُمثّل الحقل)، بعدها نعرض اللصيقة الخاصّة بالحقل، ثمّ نعرض الحقل مع تمرير المُعاملات المفتاحية Keyword Arguments ونُنبّه مُحرّك القوالب بأنّ هذا الحقل آمن (لأنّ المُحتوى مكتوب بلغة HTML وjinja يقوم بمسح مُكوّنات HTML للتأمين من هجمات XSS)، بعدها يتم التّحقّق من أنّ هناك أخطاء من المُصادقين لعرضها، وستُلاحظ أنّ الماكرو ينتهي بالكلمة المفتاحيّة endmacro للدّلالة على أنّ ذلك الجزء من الشّيفرة قد انتهى. الآن، كل ما عليك فعله هو وضع هذا الماكرو أعلى ملفّ HTML، وستستطيع استعماله باستدعائه وتمرير الحقل إليه كما تُمرّر مُعاملًا إلى دالّة عاديّة في لغة بايثون. عند استعمالك للماكرو أعلاه، تستطيع تحويل الشّيفرة الطّويلة أعلاه إلى سطرين فقط: {{ render_field(form.username) }} {{ render_field(form.password) }} خاتمة تعرّفنا في هذا الدّرس على بقيّة الحقول ومكوّنات HTML التّي تُوفّرها لنا مكتبة WTForms، وبالتّالي تستطيع الاعتماد كليّا على هذه المكتبة عوضًا عن كتابة نماذج تطبيقاتك مُباشرة بلغة HTML، وبالتّالي تأمين تطبيقاتك أكثر. في الدّرس القادم، سنتعرّف على كيفيّة التّحقق من مُدخلات المُستخدم باستخدام مبدأ المُصادقين validators الذي تمنحه لنا مكتبة WTForms للتّحقق من أنّ ما يُرسله المُستخدم إلى الخادوم يُطابق صيغة أو شكلا مُعيّنا (كبريد إلكتروني أو مقطع نصي بطول مُحدّد).
-
مُقدّمة: بعد أن تعرّفنا على أهم المفاهيم الأساسيّة حول التّحقق من مُدخلات المُستخدم ومكتبة WTForms في الدّرس السّابق، حان الوقت لاستغلال إضافة Flask-WTF مع المكتبة لإنشاء الصّنف الذي سيمثّل النّموذج. كيف تعمل مكتبة WTForms ؟ من المهم أن تدرك بأنّ استخدام مكتبة WTForms يكون على عدّة مراحل: إنشاء صنف Class خاص ليُمثّل النّموذج تقديم النّموذج باستخدام إطار العمل Flask إلى ملف HTML عرض الحقول على صفحة HTML الوصول إلى المُدخلات بعد إرسال النّموذج من طرف المُستخدم المرحلة الأولى تتم على ملف مُستقل عادة، بحيث يتم إنشاء الصّنف ثمّ تقوم باستيراده في الملفّ الذي يحتوي على الموجّهات. المرحلة الثّانيّة والمرحلة الأخيرة تتمّان على الدّالة التّابعة للموجّه. في المرحلة الثّالثة نستخدم مُحرّك القوالب Jinja لعرض النّموذج للمُستخدم. سنقوم في هذا الدّرس بالتّعرّف على أول مرحلتين، أمّا ما تبقى فسيكون في الدّرس التّالي. تعريف الصّنف المسؤول عن النّموذج أفضل ميّزة في مكتبة WTForms هي تمكيننا من إنشاء النماذج دون كتابتها مُباشرة بلغة HTML، بل نكتبها على شكل أصناف بايثون عاديّة، فهذا رائع، ويعني بأنّنا سنتمكّن من إجراء العديد من العمليات قبل أو بعد تهيئة النّماذج بلغة بايثون ما يترك مجالا كبيرا للمرونة في إنشاء النّماذج والحقول والتّحقق منها، وبالتّالي فالعمل مع المكتبة مُريح أكثر من كتابة شيفرة HTML بنفسك. وبما أنّنا سنتعامل مع النّماذج على أنّها أصناف، فإليك أولا صنفا بسيطا يُمثّل نموذج HTML يهدف إلى تمكين الزّائر من توفير اسم مُستخدم وكلمة مرور عبر حقلين ليتمكّن من تسجيل الدخول. from flask_wtf import FlaskForm from wtforms import TextField, PasswordField from wtforms.validators import DataRequired class LoginForm(FlaskForm): username = TextField('Username', validators=[DataRequired()]) password = PasswordField('Password', validators=[DataRequired()]) نسترد أولا الصّنف FlaskForm الذي تحدّثنا عنه سابقا في الدّرس السّابق من إضافة Flask-WTF، بعدها نقوم باستيراد TextField الذي يُعبّر عن حقل نصي في نماذج HTML، أي بمعنى آخر، الوسم <input> مع الخاصيّة type=text، ونسترد كذلك الصّنف الذي يُسمى PasswordField الذي يعتبر حقلا لكلمة المرور أي الوسم <input> مع الخاصيّة type=password في لغة HTML، ولاحظ بأنّنا نسترد هذه الأصناف التّي تٌعبّر عن الحقول من مكتبة WTForms وليس من إضافة Flask-WTF. بعدها نقوم باستيراد المُصَادِق DataRequired من الوحدة validators المتواجدة بالمكتبة WTForms، سيُمكّننا هذا المُصادق من التّحقق من أنّ الحقل لا يحمل قيمة فارغة عند إرسال البيانات (بمعنى آخر، سيُصبح الحقل مطلوبا). بعد استيراد ما نحتاج إليه، سنقوم بإنشاء صنف باسم LoginForm ليُمثّل النّموذج الخاص بتسجيل الدّخول، لاحظ بأنّ هذا الصّنف يرث من الصّنف FlaskForm الذي قمنا باستيراده من إضافة Flask-WTF. وبما أنّ النّموذج سيحتوي حقلين فقط، حقل لاسم المُستخدم وحقل آخر لكلمة المرور، ثمّ زر لتأكيد وإرسال البيانات Submit button، فقد قمنا بتعريفهما داخل الصّنف، إذ عرّفنا أولا حقل اسم المُستخدم بتعريف مُتغيّر باسم username ليكون كائنا من الصّنف TextField مع تمرير مُعاملين، الأول عبارة عن سلسلة نصيّة تُمثّل لصيقة الحقل أو Field Label، أمّا المُعامل الآخر فعبارة عن قائمة بالمُصادقين، وفي حالتنا فإنّ القائمة تحتوي على مُصادق واحد فقط وهو DataRequired() لجعل الحقل مطلوبا (أي منع المُستخدم من إرسال الحقل فارغا). نقوم بنفس الشيء مع حقل كلمة المرور، مع جعل الكائن من الصّنف PasswordField لتظهر مُدخلات المُستخدم على شكل نجمات عوضا عن نص واضح، كما أنّنا نُشير إلى أنّه حقل مطلوب كذلك عبر تمرير DataRequired كعنصر من قائمة إلى المُعامل validators. بالنّسبة لزر إرسال البيانات فسنقوم بكتابته مُباشرة بلغة HTML في ملفّ القالب. أين توضع الشّيفرة المسؤولة عن النّماذج؟ يُمكنك بالطّبع وضعها أينما تشاء، المهم أن تتمكّن من استيراد كل صنف على ملفّ app.py أو أي ملفّ يحمل المُوجّهات التّي ستتعامل مع النّماذج، ويُمكنك حتى وضعها في نفس الملفّ بحيث لن تحتاج إلى استيرادها، وسأعطيك مثالا بسيطا عن هذا المبدأ (كل شيء في ملفّ واحد) لاحقا. لكن من المُفضّل دائما اتّباع أفضل المُمارسات Best practices وهي ما يقوم به الجميع والمنصوح به لتوفير طريقة تطوير موحّدة ولتسهيل قراءة شيفرتك على الآخرين، وأفضل ممارسة في ما يتعلّق بالأصناف المسؤولة عن نماذج HTML هي بوضع الشّيفرة المسؤولة عن النّماذج في ملف باسم forms.py داخل مُجلّد التّطبيق لتوفير تقسيم منظّم للشّيفرة. تقديم النّموذج من المُوجّه إلى قالب HTML بعد أن قمنا بتعريف الصّنف، بقيت ثلاثة مراحل لإدارة النّموذج باستعمال مكتبة WTForms، وهذا الجزء يهم المرحلة الثّانيّة، وفيها سنقوم بتقديم النّموذج باستخدام الدّالة render_template وذلك عبر إنشاء كائن من الصّنف LoginForm الذي أنشأناه مُسبقا وتقديمه إلى القالب. لتبسيط الأمور، سأجمع الصّنف LoginForm و شيفرة التّطبيق في نفس الملف، وسيكون التّطبيق بسيطا ليفهمه الجميع. الشّيفرة الكاملة ستكون كالتّالي (قمت بفصل شيفرة النّموذج وشيفرة التّطبيق بفاصل صغير): from flask_wtf import FlaskForm from wtforms import TextField, PasswordField from wtforms.validators import DataRequired class LoginForm(FlaskForm): username = TextField('Username', validators=[DataRequired()]) password = PasswordField('Password', validators=[DataRequired()]) # --------------------------- from flask import Flask, render_template app = Flask(__name__) app.config['SECRET_KEY'] = 'T\x1f\x85\x9b\xfe^\x0f\x14\x9f\xa3x\xa4\xb5\x92\xbe' @app.route('/', methods=['GET', 'POST']) def form(): form = LoginForm() if form.validate_on_submit(): # Handle received data pass return render_template('form.html', form = form) if __name__ == "__main__": app.run(debug=True) كما تُلاحظ أعلاه، قمنا بإنشاء تطبيق بسيط نسترد فيه كلا من الصّنف Flask والدّالة render_template، ثمّ ننشئ الكائن app وبعدها نقوم بتحديد مفتاح سري لتتمكّن مكتبة WTForms من توليد حقل خفي للحماية من هجمات CSRF، وبعدها نقوم بتعريف المُوجّه ونُشغّل الخادوم في آخر سطرين. أهم شيء في التّطبيق هو الموجّه الرّئيسي ومُحتوى الدّالة form التي سنقوم من خلالها بتقديم ومُعالجة النّموذج وبياناته: @app.route('/', methods=['GET', 'POST']) def form(): form = LoginForm() if form.validate_on_submit(): # Handle received data pass render_template('form.html', form=form) أولا نقوم بتحديد أنواع الطّلبات التّي سيقبلها المُوجّه بتمرير قائمة إلى المعامل methods لإخبار فلاسك بأنّ هذا المُوجّه سيستقبل كلا من طلبات GET التّي ستُقدّم النّموذج وطلبات POST التّي ستستقبل ومن ثمّ نُعالجها البيانات. بعدها نقوم بتعريف الكائن form من الصّنف LoginForm، يُمكنك الآن تجاهل الشّرط if form.validate_on_submit() لأنّه يهتم بمُعالجة البيانات والمُصادقة عليها ولن نقوم بُمعالجتها حاليا لكن عليك أن تعلم بأنّ المُعالجة ستتمّ في مكان التّعليق Handle received data، في الأخير نقوم بتقديم الملفّ form.html مع تمرير الكائن form لنتمكّن من الوصول إليه في القالب. خاتمة أنهينا في هذا الدّرس أول مرحلتين من مراحل استخدام مكتبة WTForms للتحقّق من مُدخلات المُستخدم، فقد تعرّفنا على كيفيّة إنشاء الصّنف الذي يُمثّل النّموذج، وتعرّفنا على كيفيّة إنشاء كائن من الصّنف ثمّ تمريره لملفّ HTML، لذا سنستغل هذا الكائن في الدّرس القادم لنتمكّن من عرض النّموذج على مُتصفّح المُستخدم، وسنعرّف كذلك على كيفيّة الوصول إلى البيانات وعلى كيفيّة مُعالجتها واستغلالها بعد أن يملأ المُستخدم النّموذج ويُرسله.
-
مقدّمة بعد أن تعرّفنا على كيفيّة التّعامل مع قاعدة بياناتنا لإجراء العمليّات الشّائعة مثل الإضافة، القراءة والحذف في سلسلة مدخل إلى إطار العمل Flask فقد حان الوقت للانتقال إلى مبدأ آخر من مبادئ تطوير الويب، ألا وهو كيفيّة التّحقق من مُدخلات المُستخدم، وسنتعرّف معا في هذا الدّرس على كيفيّة استغلال كل ما تعلّمناه لنُمكّن المُستخدم الذي يستعمل تطبيقنا من إرسال بياناته إلى التّطبيق بطريقة آمنة، ومن ثمّ ستتمكّن أنت من استغلال ما ستتعلّمه لإدخال هذه البيانات إلى قواعد بيانات تطبيقاتك، وخلال هذا المشوار سنعرّف على كيفيّة مُعالجة نماذج HTML ليُرسلها المستخدم إلى الخادوم عن طريق صفحة HTML، وذلك باستخدام كل من مكتبة WTForms و إضافة Flask-WTF التّي تُسهّل لنا التّعامل مع المكتبة، والغرض من كل هذا هو التّعرف على كيفيّة إنشاء وإدارة النّماذج لاستعمالها في مختلف الصّفحات في تطبيقاتك (صفحة تسجيل الدّخول، صفحة تسجيل مستخدمين جدد، صفحة إضافة مقال …). المُتطلّبات يُعتبر هذا الدّرس مُقدّمة لسلسلة جديدة حول كيفيّة إدارة نماذج HTML عند استخدام لغة بايثون وإطار العمل Flask لبناء تطبيقات الويب، لذا فسيتوجّب عليك فهم أساسيّات إطار العمل وكذا فهم آليّة عمل نماذج HTML أو HTML Forms. إن لم تكن لك خبرة في التّعامل مع إطار العمل Flask فأنصحك بهذه السّلسلة من الدّروس. لماذا WTForms قد تتساءل عن السّبب الذي سيدفعنا لاستخدام مكتبة WTForms لإدارة نماذج HTML في تطبيقنا، ففي السّابق، كنّا نقوم بإنشاء نموذج HTML ونستخدم المُساعد request المُتواجد بإطار العمل Flask للحصول على البيانات التّي يُرسلها المُستخدم. فكنّا مثلا نقوم بالحصول على اسم المُستخدم وكلمة مروره ثمّ نتحقّق من أنّ ما يُرسله يُطابق المعطيات الصّحيحة. هذا مثال على موجّه تسجيل الدّخول بالطّريقة السّابقة: @app.route('/login', methods = ['POST']) def login(): username = request.form['username'] password = request.form['password'] if username == 'admin' and password == 'admin': # login the user ... return redirect(url_for('index')) هذه الطّريقة تعمل بشكل جيّد، لكنّها ليست آمنة تماما، إذ أنّنا لا نتحقّق من البيانات التّي يُرسلها إلينا المُستخدم. في حالة تسجيل دخول مستخدم ما، التّأكد من أنّ البيانات آمنة أو أنّها تُحقّق شروطا معيّنة أمر غير ضروري في الحقيقة لأنّنا لا نعتمد على بيانات المُستخدم سوى عند استعمال شرط منطقي، لكن ماذا لو كانت البيانات التّي يُرسلها المُستخدم بيانات تُضاف إلى قاعدة البيانات، هنا التّأكّد من أنّها تستوفي شروطا معيّنة أمر ضروري جدّا. إليك أحد أخطار عدم التّأكد من أنّ البيانات التّي يُرسلها المُستخدم آمنة قبل إدخالها إلى قاعدة البيانات، لنقل بأنّك تُتيح التّسجيل لزوار الموقع، عبر منحهم إمكانية إدخال اسم مُستخدم، بريد إلكتروني وكلمة مرور، رغم أنّ المُستخدمين عادة ما يوفّرون مدخلات معقولة إلّا أنّ عدم التّحقق منها قد يؤدي إلى قاعدة البيانات ممتلئة أو بيانات تخريبيّة بإرسال كم هائل من النّصوص كقيم للحقول الثّلاثة مثلا (اسم المُستخدم، كلمة المرور، البريد الإلكتروني)، ولو عرضت اسم المُستخدم في أحد صفحات التّطبيق فسيُخرّب الاسم الطّويل الصّفحة وستبدو غير متناسقة؛ هذا جانب من العواقب التّي قد تحدث إن لم تتحقّق من مُدخلات المُستخدم. يُمكنك بالطّبع أن تستعمل لغة بايثون لتأكيد ومعالجة قيمة كل حقل على حدة قبل إضافتها إلى قاعدة البيانات، وربّما ستستطيع تأمين مُدخلات المُستخدم بشكل أفضل من ذي قبل، لكنّ الأمر ممل ويتطلّب الكثير من الوقت، بالإضافة إلى أنّ تأكيد ومعالجة قيمة كل حقل بنفسك قد لا يكون خيارا جيّدا لأنّه قد يُؤدي إلى بعض الثغرات الأمنية وقد ترتكب أخطاء خطيرة يصعب مُلاحظتها في أول وهلة، لذا من الأفضل أن نعتمد على مكتبة معروفة وموثوقة للقيام بهذا الأمر، وهنا يأتي دور مكتبة WTForms . تُوفّر مكتبة WTForms طريقة بسيطة لإنشاء نماذج HTML وتأكيدها قبل أن تصل إلى الخادوم لنتمكّن من إرسالها إلى قاعدة البيانات، على سبيل المثال، توفّر لنا المكتبة إمكانيّة التأكّد من أنّ قيمة حقل تحتوي على عدد معيّن من الأحرف، أو تأكيد حقل بريد إلكتروني أو أنّ قيمة حقل تُساوي قيمة حقل آخر (عند تأكيد كلمة المرور مثلا). هناك بالطّبع مكتبات أخرى تؤدّي نفس الغرض (كمكتبة Deform)، لكنّنا اخترنا هذه المكتبة بالذّات لأنّ لها إضافة تُسهل علينا ربطها بالتطبيقات المكتوبة بإطار العمل فلاسك، بالإضافة إلى أنّها خيار شائع بين المُطوّرين الذين يستخدمون إطار العمل هذا، ما يعني بأنّ تعلّم كيفيّة التّعامل مع المكتبة سيُساعدك على فهم معظم المشاريع مفتوحة المصدر. الحماية من هجمات CSRF بالإضافة إلى مُساعدتنا على تأكيد البيانات وتقييدها بشروط خاصّة، فمكتبة WTForms تحمينا من هجمات CSRF (اختصار لـ Cross-site request forgery)، وترجمته ‘تزوير الطلب عبر المواقع’، وهي نوع معيّن من الهجمات تُمكّن المخترق من القيام بأمور خطيرة متنكّرا على هيئة المُستخدم الضّحيّة (الذي قد سجّل دخوله بتطبيقك)، وقد استخدمت هذه الهجمة من قبل لتحويل أموال بشكل غير شرعي من طرف مُخترق لأحد الأبناك في السّابق، وظهرت هذه الثّغرة من قبل في موقع Youtube ما خوّل للمُخترق بالقيام بجميع العمليّات التّي يُمكن أن يقوم بها المُستخدم الذي سجّل دخوله، ويُمكنك زيارة صفحة ويكيبيديا عن الهجمة لمزيد من المعلومات. الحل الأكثر شيوعا للحماية من هذا النّوع من الهجمات هو إرسال مفتاح مخفي مع البيانات التّي يُرسلها المُستخدم، بحيث أنّه إن أرسل المُخترق طلبا غير شرعيا فلن يُرسل معه المفتاح المخفي، لذا فلا يُمكن أن تمرّ البيانات إلى التّطبيق لأنّها لا تعتبر صحيحة، وبالتّالي فلا يُمكن أن تُستقبل لتُرسل إلى قاعدة البيانات، وهكذا سيكون الإجراء آمنا. الهجمة خطيرة وعليك الحذر منها عند تطويرك لتطبيقات الويب، لكن لا تقلق، فمكتبة WTForms تُوفّر لنا طريقة بسيطة لحماية تطبيقنا من هذا النّوع من الهجمات، إذ تعتمد على الحل الذي ذكرته مسبقا بحيث تُحدّد رمزا مخفيا في بداية النّموذج ليُرسل مع طلب المُستخدم في كل مرّة. التّحقق من المُدخلات بالواجهة الأماميّة أم الواجهة الخلفيّة؟ بما أنّ التّحقق من مُدخلات المُستخدم هو الاستخدام الشّائع لمكتبة WTForms (للتحقق مثلا من أن المدخل على شكل بريد إلكتروني وليس أية سلسلة نصيّة أخرى)، فمن المُفيد أن تُدرك بأنّك تستطيع التّحقق من المُدخلات على جهتين أثناء تطوير تطبيقاتك، ومن المهم أن تتبّع الأسلوب الصّحيح، لذا فهذا القسم يُغطي جزءا ممّا يجب عليك معرفته. الواجهة الأماميّة أو جهة العميل Client side مُصطلح يهم كل ما يحدث في مُتصفّح المُستخدم، وعادة ما تتم مُعالجة البيانات فيه باستخدام لغة جافاسكربت أو ميّزات HTML5 بحيث نستطيع مثلا أن نتأكّد من أنّ المُدخل المطلوب لا يُرسَل فارغا أو أنّه بطول مُعيّن وأمور أخرى. أمّا في جهة الخادوم، فالتّحقق من المُدخلات يتم باستخدام لغة بايثون، إمّا عبر كتابة الشّيفرة التّي تقوم بالتّحقق بنفسك أو بالاعتماد على مكتبات مثل مكتبة WTForms. وقد قرّرت التّطرق إلى هذا الأمر لأنّ البعض يعتمد كليا على التّحقق من البيانات بجهة العميل فقط، وبما أنّ الخادوم يستقبل المُدخلات دون التّحقق منها فهذا يعني بأنّ هناك إمكانيّة الاحتيال على المُتصفّح لإرسالها دون التّحقق منها. عندما تتحقّق من المُدخلات باستعمال جافاسكربت فقط فالأمر مُجد في كثير من الأوقات، لكن ماذا لو عطّل المُستخدم جافاسكربت على مُتصفّحه؟، ويُمكنك كذلك استخدام الميّزات الجديدة في HTML5 للتّحقق من أنّ المُدخلات صحيحة، وهذه الطّريقة يسهل الاحتيال عليها كذلك، لذا فأفضل حل هو أن تقوم بالتّحقق من مُدخلات المُستخدم في جهة الخادوم لأنّها الأكثر أمانا، وهذا يرجع إلى كون البيانات التّي نستقبلها من المُستخدم تُدخل إلى قاعدة البيانات على جهة الخادوم لذا يجب أن توقف هناك، وبالطّبع تستطيع استعمال الميّزات الجديدة في لغة HTML5 (وهذا ما أنصح به) أو أحد أطر العمل المكتوبة بلغة جافاسكربت للتّحقق من مُدخلات المُستخدم، لكن تذكّر فقط بأنّ الجهة الخلفيّة (استخدام مكتبة WTForms في حالتنا هذه) هي الأهم والأكثر أمانا. إضافة Flask-WTF لاستخدام مكتبة WTForms، سنلجأ إلى إضافة خاصّة بها لإطار العمل فلاسك، وتُسمى هذه الإضافة Flask-WTF اختصارا لـ Flask-WTForms ، لذا تأكّد من أنّ البيئة الوهميّة مُفعّلة ونفّذ الأمر التّالي لتنصيب الإضافة (والمكتبة كذلك): pip install Flask-WTF لن نستخدم الإضافة كثيرا ولن تجد استخداما لها في المشاريع التي تعتمد على إطار العمل فلاسك في معظم الحالات والاعتماد سيكون على مكتبة WTForms أكثر من الإضافة، إذ سنعتمد على الإضافة لنسترد منها صنفا واحدا فقط باسم FlaskForm لترث منها الأصناف الأخرى التّي ستُمثّل نماذج HTML، وهذا لأنّ WTForms يعتمد على الأصناف في لغة بايثون لإنشاء نماذج HTML بحيث يعبّر كل صنف عن نموذج معيّن وخصائص الصّنف هي التّي تُمثّل الحقول المُختلفة (حقل كلمة المرور، حقل البريد الإلكتروني، حقل الاختيارات المُتعدّدة …) وسنتطرّق إلى هذا الأمر بتفصيل أكثر فيما بعد. استخدام مكتبة WTForms مع أطر العمل الأخرى إنّ استخدام WTForms أمر شائع بين مُطوري الويب الذين يستخدمون أطر العمل الصّغيرة مثل Flask و Bottle و Pyramid، وحقيقة أنّ هذه السّلسلة من الدّروس موجّهة إلى مُطوري الويب الذين يستخدمون إطار العمل Flask لا تعني بالضّرورة بأنّك لن تستفيد من هذا الدّرس إذا كنت تستعمل أحد أطر العمل الأخرى، وقد قرّرت أن أجعل الأمثلة بسيطة جدا ليتمكّن الكل من فهم كيفيّة العمل المبدئيّة، كما أنّ الهدف من هذه السّلسلة هو تقريب مفهوم التحقق من بيانات المُستخدم إلى أكبر شريحة ممكنة من المُطورين لتوفير حماية أفضل لتطبيقاتهم، وبالتّالي فهي سلسلة مُستقلّة تماما عن أي سلسلة دروس أخرى، رغم أنّني أنصحك بقراءة سلسلة مدخل إلى إطار العمل فلاسك قبل الشّروع في هذه السّلسلة. تحديد مفتاح سري إن قرأت الدرس الذي شرحت فيه كيفيّة إنشاء نظام بسيط لتسجيل دخول وخروج المُستخدمين ، فقد كان علينا تحديد مفتاح سري لجعل الجلسة أكثر أمانا بحيث يصعب على المُخترق الوصول إليها، وبما أنّ توفير مفتاح سري أمر ضروري في مُعظم الأوقات (لأنّ مُعظم التّطبيقات تعتمد على الجلسة على كل حال)، فإنّ الإضافات التّي تحتاج إلى سلسلة عشوائيّة من الأحرف لاستخدامها لتوليد سلاسل أخرى مُشفّرة تلجأ إلى الإعداد SECRET_KEY. عادة ما يُسمى المفتاح السرّي مِلحًا Salt في مجال التّشفير، ويعمل على جعل السّلسلة المُولّدة ثابتة إن كان المفتاح السّري هو نفسه، فإن تغيّر المفتاح السّري (أو الملح) فإنّ فك التّشفير غير ممكن، لذا يجب أن يكون المفتاح السّري سريّا جدّا بحيث لا يُمكن توقّعه، كما يجب ألّا تضعه في مكان مكشوف في شيفرتك البرمجيّة، وتأكّد من ألّا تضع نفس المفتاح السّري الذي تستخدمه في تطبيقك على مواقع مثل Github و Pastebin وغيرها، فإن وصل أي شخص إلى مفتاحك السّري الذي تستخدمه لإدارة جلسات مُستخدميك فقد تُسرق بياناتهم أو تُخرّب. هذه المرّة، لن نحتاج إلى المفتاح السّري من أجل الجلسة، بل ستستعمله إضافة Flask-WTF لتوليد المفتاح الخفي الذي تحدّثنا عنه مُسبقا، وتستخدمه بعض الإضافات الأخرى لأغراض التّشفير كذلك، لذا فمن المُهمّ أن تُبقيّه غير قابل للتّنبّؤ. ولتوليد مفتاح سري جيّد، يُمكنك استخدام الدّالّة urandom من الوحدةos على مُفسّر بايثون كما يلي: >>> import os >>> os.urandom(16) ' T\x1f\x85\x9b\xfe^\x0f\x14\x9f\xa3x\xa4\xb5\x92\xbe' كما تُلاحظ، قمنا بتوليد سلسلة عشوائيّة من الصّعب توقّعها، وبالطّبع فعندما تُنفّذ أنت السّطر أعلاه، فستحصل على سلسلة أخرى غير السّلسلة التّي حصلت عليها. الآن كلّ ما عليك فعله هو نسخ السّلسلة النّصيّة التّي حصلت عليها ووضعها في الإعداد SECRET_KEY الخاص بتطبيق فلاسك الذي تعمل عليه، والتّالي مثال على كيفيّة إضافة هذا الإعداد. المهم أن تتأكّد من أنّ السّطر التّالي متواجد مُباشرة بعد تعريف الكائن app: app.config['SECRET_KEY'] = 'T\x1f\x85\x9b\xfe^\x0f\x14\x9f\xa3x\xa4\xb5\x92\xbe' والتّالي هو ما أعنيه بتعريف الكائن app: app = Flask(__name__) وهو نفس الكائن الذي تستعمله لتحديد موجّهات التّطبيق الأساسيّة. مُلاحظة: إن حاولت استخدام WTForms دون توفير مفتاح سري فستحصل على خطأ نصّه كما يلي: “Exception: Must provide secret_key to use csrf.”. خاتمة تعرّفنا في هذا المقال على أهم المفاهيم الأساسيّة التّي يتوجّب عليك الإلمام بها لاستكمال مشوارك في تطوير الويب، فقد تعرّفنا على الأسباب التّي ستجعلك تتحقّق من مُدخلات المُستخدم، ولمَ يُفضّل استخدام مكتبة مثل WTForms عوضا عن القيام بذلك بنفسك، وتعرّفنا كذلك على هجمة CSRF التّي تعتبر واحدة من الهجمات التّي يُمكن أن يتعرّض لها تطبيقك في أي وقت، وقد كان هذا الدّرس تمهيدا بسيطا لسلسلة من الدّروس حول التّحقق من مُدخلات المُستخدم باستخدام مكتبة WTForms.