عبدالهادي الديوري
الأعضاء-
المساهمات
368 -
تاريخ الانضمام
-
تاريخ آخر زيارة
-
عدد الأيام التي تصدر بها
22
نوع المحتوى
ريادة الأعمال
البرمجة
التصميم
DevOps
التسويق والمبيعات
العمل الحر
البرامج والتطبيقات
آخر التحديثات
قصص نجاح
أسئلة وأجوبة
كتب
دورات
كل منشورات العضو عبدالهادي الديوري
-
مُقدّمة لا شك بأنّ معظم تطبيقات اليوم تعتمد على نوع من أنواع البيانات، لذا لا بدّ من حفظها في مكان مُناسب، وتعتبر قواعد البيانات التّي يُمكن التّعامل معها بلغة SQL أحد أفضل الطّرق لحفظ بيانات تطبيقات الويب، وبما أنّنا أنشأنا قاعدة بيانات خاصّة بنا، فقد حان الوقت للتعرف على كيفية استعمال مكتبة SQLAlchemy لإدارة قاعدة بيانات تطبيقنا. في هذا الدرس، سنتعرف على كيفية تجهيز إضافة Flask-SQLAlchemy لتسهيل العمل مع مكتبة SQLAlchemy. تنصيب إضافة Flask-SQLAlchemy تأكّد أولا من أنّ البيئة الوهميّة مفعّلة عندك وأنّك في مُجلّد kalima، وبعدها نفّذ الأمر التّالي لتنصيب الإضافة مع مكتبة psycopg2: pip install Flask-SQLAlchemy psycopg2 لكي نتمكّن من التّعامل مع قاعدة بيانات PostgreSQL باستعمال SQLAlchemy يجب تنصيب مكتبة psycopg2 التّي تُمكنّنا من ذلك. مُلاحظة: تعتمد الإضافة أساسا على مكتبة SQLAlchemy لذا ستُنصّب كذلك، هذا يعني بأنّك تستطيع الوصول إلى جميع الدّوال والكائنات المُتواجدة في المكتبة إن أردت ذلك. لاختبار نجاح التّنصيب، افتح مفسّر بايثون وقم باستيراد الحزم التّاليّة: import flask_sqlalchemy import sqlalchemy إن لم يحدث أي خطأ فالحزمة مُنصّبة بنجاح، وإن واجهت أي خطأ فحاول التّأكد من أنّك اتّبعت ما ذكرته في الدّروس الأولى من السّلسلة بشكل صحيح. تذكير قواعد البيانات من نوع SQL تعتمد بشكل رئيسيّ على الجداول Tables، كلّ جدول يحمل أعمدة مُختلفة، وكلّ عمود يحمل قيما معيّنة، وهناك عمود خاصّ يسمى برقم المُعرّف وعادة ما يُشار إليه بـid ولأنّ كل مُدخل من المُدخلات يحمل رقم مُعرّف فريدا، فسيُمكّننا من تمييز المُستخدمين والمقالات وبقيّة الأجزاء المُهمّة في تطبيقاتنا، وقد سبق وأن شرحت هذا في درس ربط تطبيق Flask مع قاعدة بيانات SQLite، لذا عد إلى ذلك الدّرس لتفهم أكثر. ربط التّطبيق بإضافة Flask-SQLAlchemy لربط التّطبيق مع إضافة Flask-SQLAlchemy نقوم أولا باستيراد SQLAlchemy من حزمة الإضافة بالسّطر التّالي: # project/__init__.py from flask_sqalchemy import SQLAlchemy وبعدها نقوم بتمرير كائن التّطبيق app إلى ما قمنا باستيراده وإسناد النّاتج إلى مُتغيّر باسم db كما يلي: # project/__init__.py db = SQLAlchemy(app) سنقوم كذلك بإضافة سطرين لإعدادات التّطبيق app.config، السّطر الأول سيكون عبارة عن رابط قاعد البيانات، كما يلي: app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql://username:password@127.0.0.1/kalima' استبدل username بالمُستخدم الذي أنشأت به قاعدة البيانات وكلمة المرور بكلمة المرور التّي سبق أن حدّدتها، إن كنت على أحد أنظمة لينكس فالمُستخدم المبدئي هو postgres والعنوان 127.0.0.1 يُشير إلى أنّنا سنعتمد على قاعدة بياناتنا المحليّة، أمّا kalima فهو اسم قاعدة البيانات التّي أنشأناها سابقا. السّطر الثّاني غير مُهم وقد لا تحتاج إليه في قادم النّسخ من إضافة Flask-SQLAlchemy، وقد قمت بإضافته لإخفاء تنبيه يظهر بعد تشغيل الخادوم. app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True مُلاحظة: من المعروف بأنّ التّطبيق يمر عبر عدّة مراحل، مرحلة التّطوير، الاختبار ثمّ مرحلة الإنتاج (النّشر إلى العالم الخارجي)، وعادة ما تُعزل كلّ مرحلة عن الأخرى فيما يُسمّى بالبيئة، ولذا تسمع مُصطلحات كبيئة التّطوير Development Environment، بيئة الاختبار Testing Environment، بيئة الإنتاج Production Environment، وإعدادات كلّ بيئة تكون مختلفة عن البيئات الأخرى، ولهذا يعدّ تخزين الإعدادات في الملفّ الرّئيسي للمشروع أمر سيئا لأنّك ستضطر إلى تغيير الإعدادات في كل مرّة تنتقل فيها من بيئة إلى أخرى، وقد يُشكل إعداد خاطئ واحد خطرا كبيرا على تطبيقك، خاصّة إن كان التّطبيق ذا وظائف مُتعدّدة، والحل الأمثل هو تخزين هذه الإعدادات في ملفّ مُستقل وهذا الأمر مهم جدا وقد سبق وأن شرحت طريقة بسيطة للقيام بذلك، وسنتطرّق إليه بالتّفصيل في ما بعد، المهم الآن أن تُدرك بأنّ كل ما يقع تحت قاموس app.config عبارة عن إعداد من إعدادات التّطبيق. بعد ربط كائن التّطبيق بإضافة Flask-SQLAlchemy سنتمكّن من استغلال الكائن db للاستفادة من كل ما تمنحه لنا الإضافة من تسهيلات للعمل مع مكتبة SQLAlchemy. ومع التّغييرات التّي قُمنا بها سيُصبح الجزء العلوي من ملفّ project/__init__.py كالآتي: # project/__init__.py from flask import Flask, render_template app = Flask(__name__) app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql://username:password@127.0.0.1/kalima' app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True from flask_sqlalchemy import SQLAlchemy db = SQLAlchemy(app) from project.posts.views import posts from project.users.views import users app.register_blueprint(posts) app.register_blueprint(users) تأكّد فقط من أنّ الإعدادين يقعان مُباشرة بعد تعريف الكائن app وأنّ استيراد الطّبعات الزّرقاء Blueprints وتسجيلها يقع بعد تعريف المُتغيّر db لتجنّب بعض المشاكل التّي قد تحدث عندما تعتمد كل حزمة على الأخرى أثناء الاستيراد أو ما يُسمى بـ circular imports أو الاستيرادات الدّائريّة. الجداول في Flask-SQLAlchemy لتجهيز الجداول في التّطبيق، سنفتح أولا ملفّا جديدا باسم models.py في مجلّد project، وسنضع فيه كل ما يتعلّق بالجداول وطريقة تسجيلها والبيانات المبدئية. وطريقة إنشاء الجداول في Flask-SQLAlchemy هي كالآتي: from project import db class TableName(db.Model): __tablename__ = 'table_name' column_name = db.Column(db.Type, args) كما ترى، نقوم أولا باستيراد الكائن db من حزمة المشروع ثمّ نقوم بإنشاء صنف باسم الجدول، ولابد لهذا الصّنف أن يرث من الصّنف db.Model الذي تُوفّره لنا إضافة Flask-SQLAlchemy، بعدها نقوم بتسميّة الجدول عبر وضع اسمه على شكل سلسلة نصيّة وإسنادها للمُتغيّر الخاصّ __tablename__، ثمّ بعد ذلك نقوم بتعريف مُختلف أسماء الأعمدة وإسنادها قيمة عن طريق التّابع db.Column، ثمّ نُخصّص نوع قيم العمود وبعض المُعاملات الإضافيّة، وأحد أشهر المُعاملات هو مُعامل primary_key الذي يجعل من العمود مفتاحا أوليا إن أسندت له القيمة المنطقيّة True وعادة ما يُستخدم مع عمود رقم المُعرّف id، والمُعامل الآخر الذي ستصادفه كثيرا هو مُعامل nullable ويقبل كذلك القيمتين المنطقيّتين True و False فإن كانت قيمة المُعامل False فهذا يعني بأنّ العمود لا يمكن أن يحمل قيمة فارغة، ويُستعمل في الأعمدة المُهمّة كالبريد الإلكتروني وكلمة المرور، ويُمكن أن تسمح بأن تكون قيم العمود فارغة إن لم يكن توفير المعلومة ضروريا مثل نبذة عن المُستخدم وصورته الشّخصيّة أو اسمه الحقيقي وهذه الأمور تختلف حسب نوعيّة التّطبيق بكل تأكيد. المفتاح الأولي Primary Key عبارة عن عمود يحمل قيما فريدة في الجدول بأكمله، ولا يُمكن أن تكون قيمته فارغة ولكنّنا لا نحتاج إلى إضافة المُعامل nullable عند تعريف العمود، ويُستعمل لتعريف كل سجلّ من سجلّات الجدول على حدة، ومن المُفضّل أن يكون نوعه عددا صحيحا لحفظ بعض المساحة، ولإبقاء القيم فريدة ومُختلفة عن بعضها البعض، يضاف واحد إلى القيمة السّابقة عند كل إضافة إلى قاعدة البيانات، وكما قُلت سابقا، الطّريقة الشّائعة لاستخدامه هو بوضع عمود باسم id في الجدول ليحمل القيم الفريدة على شكل أعداد صحيحة، فمثلا لو كان لدينا قاعدة بيانات بأسماء ما كما يلي: id | name 1 | أحمد 2 | مُحمّد 3 | أحمد للوهلة الأولى يبدو بأنّ سجلّ أحمد قد كرّر مرّتين، لكن لاحظ رقم المُعرّف الخاص بكل سجلّ، أحمد الأول رقم مُعرّفه 1 والآخر 3، هذا يعني بأنّهما شخصان مُختلفان بنفس الاسم، لا بد أنّك أدركت الآن مدى أهميّة المفتاح الأولي، فلو كان تطبيقنا يسمح بتسجيل المُستخدمين بأسمائهم لوجدنا مُشكلة مع أحمد هذا، لكنّ رقم المُعرّف يقوم بحل المُشكلة. أمّا عن db.Type فهو لتحديد نوع القيم التي سيحملها العمود، وهي مُشابهة لأنواع القيم المُتاحة في لغة SQL، مثل العدد الصّحيح Integer والسّلسلة النّصيّة String (VARCHAR في لغة SQL) والتّاريخ وغير ذلك من أنواع القيم. خاتمة قمنا في هذا الدرس بتجهيز أداة SQLAlchemy وتعرّفنا على كيفيّة استخدام إضافة Flask-SQLAlchemy للتعامل معها وألقينا نظرة بسيطة إلى الصورة العامة لجدول SQL مكتوب بلغة بايثون. في الدرس القادم، سنقوم بإنشاء جدولي المقالات والمُستخدمين في قاعدة بياناتنا.
-
تجهيز قاعدة البيانات بما أنّنا سنستخدم قاعدة البيانات PostgreSQL لهذا التّطبيق، عليك أولا أن تقوم بتنصيبها، إن كنت تستخدم Ubuntu فيُمكنك مُراجعة درس كيفيّة تنصيب PostgreSQL على Ubuntu، أمّا إن كنت على نظام Windows أو نظام Mac OS فتستطيع تحميلها من الموقع الرّسمي وتنصيبها، يُمكنك كذلك استخدام برنامج pgAdmin لإدارة قواعد بياناتك عن طريق برنامج رسومي. يُمكنك استعمال البرنامج الرّسومي لإنشاء قاعدة البيانات إن أردت ذلك، لكنّنا سنستعمل أداة psql لتنفيذ أوامر PostgreSQL واستعمال لغة SQL مباشرة من سطر الأوامر، لذا تأكّد من أنّك تستطيع الوصول إلى أداة psql. إنشاء قاعدة البيانات لإنشاء قاعدة البيانات، يكفي أن ننفّذ الأمر التّالي على سطر أوامر psql : CREATE DATABASE kalima; بعد تنفيذ الأمر، سيكون المُخرج كما يلي: CREATE DATABASE كلمة المرور للاتّصال بقاعدة البيانات، سنحتاج إلى كلمة مرور، إن سبق لك وأن خصّصت كلمة مرور أثناء التّنصيب، تستطيع تخطي هذه الخطوة، أمّا إن لم تمتلك كلمة مرور أو أنّك نسيتها، فيُمكنك ببساطة تغييرها في سطر أوامر psql بالأمر التّالي: \password بعد تنفيذ الأمر سيُطلب منك كتابة كلمة مرور جديدة وإعادة كتابتها للتّأكد من أنّك لم تُخطئ. كلمة المرور هذه مُهمّة جدا للاتّصال بقاعدة البيانات من تطبيق فلاسك باستخدام أداة SQLAlchemy لذا تأكّد من أنّها مُتاحة. ما معنى ORM؟ ORM اختصار Object-Relational Mapper أي رابط الكائنات بالعلاقات (الجداول)، الهدف منه ببساطة التّعامل مع قواعد بيانات SQL باستعمال الكائنات Objects والأصناف Classes عوضا عن تعليمات SQL، فعوضا عن إنشاء البيانات وجلبها وتعديلها وحذفها عن طريق تعليمات SQL التّي ستتكرّر كثيرا في الشّفرة يُمكن ببساطة استخدام لغة بايثون للوصول إلى نفس الهدف، كما أنّ استعمالها مع لغة برمجة يخلق شيئا من عدم التّناسق. لو تابعت سلسلة دروس Flask للمُبتدئين، للاحظت بأنّنا استعملنا ملفّا باسم manage_db للتّعامل مع قواعد البيانات عن طريق الدّوال عوضا عن كتابة تعليمات SQL عند الرّغبة في إجراء كل عمليّة، ولو استعملنا SQL الخام لكان تكرار الشّفرة كثيرا. بالإضافة إلى ميّزة تعريف الأصناف التّي تُمثّل الجداول والتّعامل معها عن طريق التّوابع (الدوال) لتجنّب تكرار الشّفرة، يقوم الـORM بالكثير من الأشياء المُملّة خلف الكواليس، فعوضا عن الاهتمام بالتّفاصيل الصّغيرة بأنفسنا، يقوم الـORM بالاهتمام بها من أجلنا لنهتمّ بالأمور الأكثر أهميّة، وفي النّهاية، مُعظمنا لا يتقن لغة SQL كثيرا لذا من المُفضّل استعمال مكتبة للتّعامل مع قواعد البيانات باللغة التّي نُجيدها. وبما أنّ استعمال الـORM يُتيح لنا عزل الشّفرة المسؤولة عن التّعامل مع قاعدة البيانات في معزل عن التّطبيق فهذا يُتيح لنا تعديل الخصائص وإضافة الميّزات للتّطبيقات دون التّأثير على تطبيق معيّن بحد ذاته، ومن الشّائع جمع الشّفرة المسؤولة عن التّعامل مع قاعدة البيانات في ملف باسم models.py في مجلّد المشروع. يُساعدنا الـORM كذلك على تحقيق مبدأ MVC أو Model-View-Controller أي “نموذج، عرض، مُتحكّم ” وهو مبدأ يقوم على عزل جزء العرض، المُتحكّمات، والنّماذج في تطوير التّطبيقات لمرونة أكثر في التّطوير، أي أنّ كلّا من العرض (الذي يُمثّل القوالب في تطبيقنا) والمُتحكّم (الذي يُمثّل دوال الموجّهات في ملفّات views.py) والنّموذج (أي الجزء الذي يتعامل مع البيانات) أجزاء منفصلة حسب الوظيفة متّصلة في التّطبيق ويُمكن أن تنتقل البيانات بينها ببساطة، ورغم أنّ إطار Flask يمنحك الحريّة الكاملة في طريقة تسميّة أجزاء تطبيقك، إلّا أن أطر عمل أخرى مثل Django و Rails تفرض عليك تسميّات محدّدة. ما هو SQLAlchemy؟ بعد أن عرّفنا معنى الـORM بقي تعريف مكتبة SQLAlchemy، وهي مكتبة تُسهّل علينا التّعامل مع قواعد البيانات المُختلفة، كما أنّها تحتوي على ORM قوي ومُعتمد عليه من طرف المشاريع الكبيرة. تحتوي SQLAlchemy على الكثير من الدّوال المُساعدة والكائنات التّي تُسهّل عليك كتابة SQL بلغة بايثون، والORM المبني فيه يُستعمل لربط الكائنات بالبيانات في قاعدة البيانات. سنستعمل في غالب هذا المشروع الـORM الخاص بـSQLAlchemy ولن نتطرّق إلى بقيّة ميّزات المكتبة، لذا إن أردت معرفة المزيد عن المكتبة فعليك بالتّوثيق الرّسمي. إضافات Flask Flask Extensions أو إضافات فلاسك عبارة عن مكتبات وأطر عمل Frameworks مبنيّة لتُسهّل على المُطوّر القيام بالأشياء المُفصّلة وتجنّب التّعامل مع كل شيء بأنفسنا، فمثلا، يُمكننا أن نستعمل مكتبة SQLAlchemy مع تطبيق Flask مُباشرة، إلّا أن في هذه الطّريقة الكثير من الأمور غير المُهمّة والكثير من الإعدادات، وبما أنّنا في مسيرة تعلّم إطار فلاسك لا كيفيّة استعمال SQLAlchemy، ولتسهيل استعمال SQLAlchemy مع المشاريع المبنيّة بإطار العمل فلاسك، قام كاتب الإطار Armin Ronacher بتطوير إضافة للإطار باسم Flask-SQLAlchemy التّي أصبحت أحد أشهر الإضافات على موقع Github. بالإضافة إلى Flask-SQLAlchemy هناك الكثير من الإضافات التّي يُمكننا استعمالها لتطوير تطبيقات فلاسك، وإليك بعضا من هذه الإضافات: Flask-Login إضافة لتسهيل التّعامل مع جلسات المُستخدمين Sessions، تُتيح إمكانيّة تسجيل دخول مُستخدم وتسجيل خروجه، لكنّها لا تقوم بالتّحقق من صحّة البيانات (اسم المُستخدم وكلمة المرور مثالا) وتترك المُطوّر للاهتمام بالأمر. Flask-Bcrypt إضافة لتشفير كلمات المرور باستعمال خوارزميّة Bcrypt قبل حفظها بقاعدة البيانات لحماية أكثر، هذا لمنع المُخترق من سرقة كلمات المرور حتى بعد وصوله إلى قاعدة البيانات، وكلّما كانت خوارزميّة التّشفير أعقد كلّما كان من الصّعب فك التّشفير عن كلمة المرور ما يُوفّر حماية أكثر. Flask-WTF بما أنّ مُعظم التّطبيقات تعتمد على نماذج HTML (حقول النّصوص، كلمات المرور و مساحة النّص TextArea) وبما أنّها تُسبّب الكثير من الثّغرات الأمنيّة في التّطبيقات والتّي يُمكن أن يُساء استغلالها، فلا بد من توفير طريقة آمنة وبسيطة للتأكّد من أنّ ما يُدخله المُستخدم في هذه الحقول آمن. يُمكنك بالطّبع التّأكد من مُدخلات المُستخدم بنفسك، لكنّ الأمر يأخذ وقتا، كما أنّه ممل في الحقيقة، إذ عليك تكرار نفس الشّيء كلّ مرّة. عوضا عن الاعتماد على مهاراتك البرمجيّة للتأكّد من سلامة المُدخلات، يُمكنك أن تترك إضافة Flask-WTF للاهتمام بالأمر في تطبيقاتك المبنيّة بإطار فلاسك، الإضافة مبنيّة على مكتبة WTForms التّي يُمكنك استعمالها مع أطر عمل أخرى، وأنصحك بالاطلاع على توثيق المكتبة الرّسمي للمزيد من المعلومات عن كيفيّة مُعالجة النّماذج. Flask-Migrate ببساطة، Flask-Migrate إضافة تُتيح لك تهجير البيانات Migrations عند تطوير تطبيقك. هذه الإضافة مهمّة جدّا إن كنت تطوّر تطبيقا سبق وأن نشرته على نحو مُتواصل، إذ تُتيح لك تهجير البيانات في كل مرّة تُضيف جدولا جديدا أو علاقة بين الجداول إلى قاعدة البيانات. تخيّل أنّك تمتلك تطبيقا يحتوي جدول المُستخدمين فيه على أكثر من ألف مُدخل، إن أردت إتاحة إمكانيّة إضافة تاريخ ازدياد للمُستخدمين فستضطر إلى حذف كلّ شيء في هذا الجدول وإضافة العمود الجديد ثمّ إعادة البيانات من جديد، الأمر مُعقّد بعض الشّيء وفقدان البيانات أمر غير مرغوب فيه بتاتا. مُجدّدا عوضا عن القيام بكل شيء بنفسك، دع إضافة Flask-Migrate لتهتمّ بكل هذا التّعقيد من أجلك، فبسطر أو سطرين تستطيع تطبيق الميّزة الجديدة لتطبيقك وتهجير البيانات القديمة بكل بساطة. Flask-Testing إضافة لتسهيل إجراء اختبارات وحدة Unit tests لتطبيقك، إذ أنّ اختبار التّطبيق مُهمّ جدّا للتأكّد من أنّ كلّ شيء بخير وأنّ المُستخدمين لا يواجهون أية مشاكل، خاصّة إذا كان مشروعك كبيرا، فاختبار كل وظيفة يدويّا قد يأخذ منك وقتا طويلا، وإن لم تختبره آليا فنسبة الخطأ كبيرة جدّا. إذا أضفت خاصيّة جديدة لتطبيقك فأنت بذلك تُعرّض أجزاء أخرى من التّطبيق إلى الخطأ، وستُسهّل عليك الاختبارات معرفة ما إذا كان التّطبيق لا يزال يعمل كما ينبغي له أو لا، وبما أنّ اختبارات الوحدة تختبر كلّ وظيفة على نحو مُستقل، فسيسهل عليك معرفة أي جزء يجب عليك الاهتمام به لإصلاح التّطبيق. Flask-Gravatar هذه الإضافة تُسهّل لنا استعمال خدمة Gravatar لتمكين المُستخدمين من إضافة صورة شخصيّة لهم، إذ تكون الصّورة الشّخصيّة مُرتبطة بالبريد الإلكتروني للمُستخدم وتُستعمل هذه الخدمة في الكثير من التّطبيقات في وقتنا الحالي. هناك مئات الإضافات الخاصّة بإطار Flask لذا لا يُمكنني ذكرها كلّها، إن أردت الاطّلاع على إضافات أخرى، تستطيع البحث عنها عن طريق أداة pip كما يلي: pip search flask سنتعرّف على معظم هذه الإضافات في قادم الدّروس مع التّقدم في المشروع، وهذا الهدف الأساسي من هذه الإضافات، كلّما تقدّمت أكثر وكبر المشروع كلّما احتجت إلى التّعامل مع الكثير من المهام المُملّة كالتّأكد من أنّ ما يرسله المُستخدم إلى قاعدة بياناتك آمن، وكالتّعامل مع الجلسات لتوفير نظام تسجيل دخول آمن دون الاضطرار للقيام بكل التّفاصيل؛ كما أنّ الإضافات تُقلّل من حجم الخطأ في التّطوير، فالاعتماد على إضافة لتسجيل دخول المُستخدمين بشكل آمن أفضل من مُحاولة إنشاء طريقة خاصّة بك قد تكون مليئة بالثّغرات الأمنيّة. وبما أنّ فلاسك إطار عمل مُصغّر، فبإضافاته يُمكنك أن تستخدمه كما تُستخدم أطر العمل الضّخمة كـDjango و Rails و Laravel وغيرها من الأطر المشهورة. خاتمة بعد أن أنشأنا قاعدة البيانات وتعرّفنا على بعض المفاهيم المُهمّة في تطوير تطبيقات الويب مع إطار فلاسك، حان الوقت لإنشاء الجداول الأساسيّة في قاعدة البيانات لنتمكّن من التّعامل معها عن طريق إضافة Flask-SQLAlchemy وهذا بالضّبط ما سنقوم به في الدّرس القادم.
-
مُقدّمة تعرّفنا سابقا على حلقة التّكرار for للدّوران على عناصر قائمة أو مفاتيح وقيم قاموس بايثون، سنتعرّف الآن على تقنيات مُتقدّمة ستُساعدك على التّعامل مع حلقة for على نحو أفضل لتتمكّن من تصميم قوالب HTML مع مُحرّك القوالب Jinja بطريقة أسرع وأكثر إتقانا. استعمال حلقة for مع قائمة بايثون تُستعمل حلقة for مع قوائم بايثون عاديّة كما يلي: {% set names = ['Ahmed', 'Khalid', 'Ali', 'Abdelhadi', 'Yasser'] %} <ul> {% for name in names %} <li>{{ name }}</li> {% endfor %} </ul> النّتيجة: Ahmed Khalid Ali Abdelhadi Yasser بهذه الطّريقة، يُمكنك عرض كل عنصر من القائمة داخل وسم HTML مُعيّن وتطبيق تنسيق باستخدام CSS كما تشاء. استعمال حلقة for مع قواميس بايثون بالإضافة إلى استعمالها مع قائمة عاديّة، يُمكنك كذلك استعمال حلقة for مع قاموس ما للوصول إلى مفاتيحه وقيّم كلّ مفتاح بحيث تدور حول القاموس لتحصل على كل مفتاح وقيمته، يُمكنك القيام بذلك ببساطة كالتّالي: {% set website = { 'url': 'academy.hsoub.com', 'company': 'Hsoub', 'description': 'Learn new skills for free' } %} <ul> {% for key, value in website.iteritems() %} <li> {{ key }} : {{ value }} </li> {% endfor %} </ul> لاحظ بأنّنا نقوم بالوصول إلى محتويات القاموس عبر تطبيق التّابع iteritems() على المُتغيّر website، بحيث نحصل على المفتاح والقيمة عبر المُتغيّرين key و value على التّوالي، داخل حلقة for نقوم بعرض كلّ من المفتاح وقيمته داخل وسم <li>. النّتيجة: url : academy.hsoub.com company : Hsoub description : Learn new skills for free يُمكنك كذلك استخدام تنسيقات إطار العمل Bootstrap لعرض مُحتويات قاموس ما بشكل جميل. بالإضافة إلى ما سبق، يُمكنك كذلك إسناد قائمة بايثون على هيئة قيمة لمفتاح مُعيّن من القاموس، على سبيل المثال، يُمكن لمقال ما أن يحمل عنوانا واحدا وأكثر من تعليق واحد، بحيث تكون التّعليقات عناصر من قائمة بايثون كما يلي: {% set post = {'title': 'Introduction to Flask', 'comments': ['Great!', 'Thank you!'] } مثال تطبيقي لنجمع الآن الأفكار السّابقة ولنُصمّم صفحة HTML بسيطة لعرض معلومات شخص ما: {% set person = { 'first_name': 'Ali', 'last_name': 'Sayed', 'age': '27', 'languages': ['Arabic', 'English'], 'Children': ['Hassan', 'Fatima'], } %} <div class="col-sm-4"> <ul class="list-group"> <li class="list-group-item active"> {{ person['first_name']}} {{ person['last_name']}} </li> {% for key, value in person.iteritems() %} {% if value is string %} <li class="list-group-item">{{ key }} : {{ value }}</li> {% else %} <li class="list-group-item"> {{ key }} : {% for item in value %} {{ item }}, {% endfor %} {% endif %} </li> {% endfor %} </ul> </div> رغم أنّ الشّفرة مُعقّدة بعض الشّيء، إلّا أنّها في الحقيقة بسيطة، وستبدو لك كذلك إن تابعت الفقرتين السّابقتين من هذا الدّرس. إليك ما يحدث في الشّفرة أعلاه، أولا نُعرّف قاموسا باسم person ليحمل المفاتيح التّالية: الاسم الأول للشّخص، الاسم العائلي (النّسب)، العمر، اللغات التي يُتقنها الشّخص، أطفاله. لاحظ بأنّ قيم اللغات والأطفال عبارة عن قوائم بايثون. في شفرة HTML نقوم بالدّوران حول مُحتويات القاموس person، بعدها نستعمل الاختبار string المُعرّف قياسيا في Jinja للتّحقق ممّا إذا كانت القيمة عبارة عن سلسلة نصيّة أو لا، إن كانت كذلك فإنّنا نعرضها مع مفتاحها بشكل طبيعي، إن لم تكن القيمة سلسلة نصيّة فهذا يعني بأنّها قائمة من عناصر مُتعدّدة وأنّ الجزء الذي يلي الكلمة المفتاحيّة else هو الذي سيُعرَض. في الجزء else نقوم بعرض المفتاح ثمّ نستعمل الحلقة for مُجدّدا للدّوران على عناصر القائمة المُتواجدة في قيمة المفتاح المذكور آنفا. هذا هو الجزء الخاص بعرض عناصر قائمة بعد قيمة المفتاح: {% else %} <li class="list-group-item"> {{ key }} : {% for item in value %} {{ item }}, {% endfor %} إن استخدمت إطار العمل Bootstrap فستكون النّتيجة مُشابهة لما في الصّورة التّاليّة: لاحظ بأنّ القاموس غير مُرتّب على نحو صحيح، ذلك لأنّ قواميس بايثون ليست مُرتّبة مبدئيًّا، لكن يُمكنك ترتيب مُحتويات القاموس أبجديا عبر استعمال المُرشّح dictsort كما يلي: {% for key, value in person | dictsort %} مع مُلاحظة أنّك لن تحتاج إلى التّابع iteritems() عند استعمال المُرشّح. المُتغيّر loop عند استخدام الحلقة for حلقة for تعمل بطريقة بسيطة، إذ تأخذ عدّة قيم وتمنحك كل قيمة داخل مُتغيّر مؤقّت، وفي كلّ دورة تتغيّر قيمة المُتغيّر. يوفّر مُحرّك القوالب Jinja عند التّعامل مع حلقة for مُتغيّرا خاصّا داخل الحلقة باسم loop؛ باستعمال هذا المُتغيّر الخاص، يُمكنك الوصول إلى معلومات خاصّة بالدورة الحاليّة وتُمكّننا كذلك من استعمال تقنيّات الاستدعاء الذّاتي (Recursion) لتضمين عناصر قائمة داخل عناصر قائمة أخرى خاصّة بالدّورة الحاليّة (ستتضّح الصّورة أكثر عندما ننتقل إلى الأمثلة التّطبيقية). الوصول إلى رقم الدّورة الحاليّة يُمكنك استخدام الخاصيّة index مع المُتغيّر loop في الحلقة for كما يلي: {% for item in items %} {{ loop.index }} {% endfor %} مثال: {% set list = ['Python', 'Flask', 'Jinja', 'Programming', 'Web developement'] %} {% for item in list %} <p>{{ loop.index }} - {{ item }}</p> {% endfor %} النّتيجة: 1 - Python 2 - Flask 3 - Jinja 4 - Programming 5 - Web development لاحظ بأنّ العدّ يبدأ من واحد، إن أردت أن يبدأ العدّ من الصّفر، سيتوجّب عليك استخدام التّابع index0 عوضا عن التّابع index كما يلي: {{ loop.index0 }} بهذا التّعديل ستكون النّتيجة كما يلي: 0 - Python 1 - Flask 2 - Jinja 3 - Programming 4 - Web developement الوصول إلى رقم الدّورة الحاليّة عكسيّا يُمكنك كذلك عكس تأثير التّابع index عبر استعمال التّابع revindex كالتّالي: {{ loop.revindex }} النّتيجة: 5 - Python 4 - Flask 3 - Jinja 2 - Programming 1 - Web developement وللانتهاء بالرّقم 0 يُمكنك استعمال التّابع revindex0. التحقّق مما إذا كانت الدّورة الحاليّة هي الأولى أو الأخيرة يُمكنك استعمال التّابع first الذي يُرجع القيمة True إذا كانت الدّورة الحاليّة هي أول دورة في حلقة for. وبنفس الطّريقة فإنّ التّابع last يُساعد على التّحقّق ممّا إذا كانت الدّورة الحاليّة هي الأخيرة أو لا. مثال: {% for item in list %} {% if loop.first %} <p>{{ loop.index }} (First) - {{ item }}</p> {% elif loop.last %} <p>{{ loop.index }} (Last) - {{ item }}</p> {% else %} <p>{{ loop.index }} - {{ item }}</p> {% endif %} {% endfor %} النّتيجة: 1 (First) - Python 2 - Flask 3 - Jinja 4 - Programming 5 (Last) - Web developement الوصول إلى عدد عناصر القيم الإجمالي بالإضافة إلى إمكانيّة الحصول على عدد عناصر قائمة ما باستعمال المُرشّح count أو المُرشّح length، يُمكنك كذلك استخدام loop.length للحصول على عدد عناصر القيم التي اِستُخْدِمَتْ مع حلقة for الحاليّة. يُمكنك التّحقق من الأمر عبر المثال التّالي (على افتراض أنّك تستعمل القائمة list السّابقة): <p>{{ list | length }}</p> <p>{{ list | count }}</p> {% for item in list %} <p>{{ loop.length }} - {{ item }}</p> {% endfor %} ستُلاحظ بأنّ النّتيجة هي نفسها سواء استخدمنا المرشّحات أو التّابع loop.length، بهذه الطّريقة يُمكنك الحصول على عدد عناصر قائمة من داخل حلقة for. إعادة استعمال نفس الحلقة مع قائمة داخل قائمة أخرى لنفترض بأنّنا نُريد عرض أسماء أشخاص مع أبنائهم بحيث تكون قائمة HTML الخاصّة بالأبناء تابعة للأب. إليك البيانات التّي سنستعملها في مثالنا هذا: {% set people = [ dict(name='Ali', children=[dict(name='Mohammed'), dict(name='Hassan')]), dict(name='Khaled', children=None), dict(name='Fahd', children=[dict(name='Kamal'), dict(name='Omar')]) ] %} في القائمة أعلاه، لدينا ثلاثة قواميس، القاموس الأول خاصّ بعلي، وأطفاله (الذين يعتبرون قائمة من القواميس كذلك) محمد وحسن، أما القاموس الثّاني فخاص بخالد الذي ليس لديه أبناء، والقاموس الأخير خاص بفهد، وأبناؤه كمال وعمر. لعرض أسماء الآباء الذين يتواجدون في القواميس الثّلاثة، يُمكن أن نستخدم حلقة for بسيطة، لكن سيتوجّب علينا تكرار الشّفرة إذا ما أردنا الدوران حول أبناء كل شخص. عوضا عن استخدام أكثر من حلقة for واحدة، يُمكننا استخدام حلقة for خاصّة تُكرّر نفسها، ويُمكننا فعل هذا الأمر عبر إضافة الكلمة recursive عند استخدام حلقة for كالتّالي: {% for person in people recursive %} هكذا سنتمكّن من استعمال المُتغيّر loop لإعادة الدّوران حول قائمة أطفال كل شخص، لكن سنفعل ذلك فقط إن كان للشّخص أبناء بالفعل، لذا سنتحقّق من أنّ القيمة لا تُساوي القيمة None عبر الاختبار كما يلي: {% if person.children is not none %} <ul class="list-group"> {{ loop(person.children) }} </ul> {% endif %} المثال النّهائيّ سيكون كالتّالي: {% set people = [ dict(name='Ali', children=[dict(name='Mohammed'), dict(name='Hassan')]), dict(name='Khaled', children=None), dict(name='Fahd', children=[dict(name='Kamal'), dict(name='Omar')]) ] %} <div class="col-sm-4"> <ul class="list-group"> {% for person in people recursive %} <li class="list-group-item"> {{ person.name }} {% if person.children is not none %} <ul class="list-group"> {{ loop(person.children) }} </ul> {% endif %} </li> {% endfor %} </ul> </div> النّتيجة ستكون كما في الصّورة التّاليّة: يُمكنك كذلك معرفة عمق الحلقة باستخدام التّابع loop.depth داخل حلقة for من النّوع recursive كالتّالي: <span class="badge">{{ loop.depth }} </span> تأكّد فقط من أن تضع السّطر السّابق مُباشرة بعد السّطر: {% for person in people recursive %} النّتيجة ستكون كالتّالي: لاحظ بأنّ رقم العمق يبدأ من واحد، إن أردت أن يبدأ من الصّفر فاستعمل loop.depth0. يُمكنك كذلك استخدام هذه الميّزة مع تعليقات في مُدوّنة أو منصّة تتطلّب تفاعل الأعضاء بين بعضهم البعض. الصّورة التّاليّة توضيح بسيط للنّتيجة النّهائيّة التي يُمكنك أن تصل إليها إن كنت تمتلك تعليقات متداخلة في قاعدة بياناتك: خاتمة تعرّفنا في هذا الدّرس على كيفيّة استخدام حلقة for في مُحرّك القوالب Jinja للحصول على قوالب HTML أحسن وتطويرها بسرعة. وبهذا نكون قد أكملنا أساسيّات دروس مُحرّك القوالب Jinja وسننتقل في الدّروس القادمة للتّعرف على جزء آخر مُهمّ في تطوير الويب، ألا وهو كيفيّة التّعامل مع قواعد البيانات في التّطبيقات المكتوبة بإطار العمل Flask.
-
مُقدّمة تعرّفنا في الدّروس السّابقة على العديد من ميّزات مُحرّك القوالب Jinja وكيفيّة استخدامها لتصميم صفحات HTML بسرعة، إذ بدأنا بمبدأ المرشّحات ثمّ مررنا على الاختبارات لنُنهي مشوارنا عبر التّعرف على طرق لتفادي تكرار شفرة HTML وإعادة استخدام القوالب أو أجزاء منها في مشروع مُعيّن، بل يُمكنك كذلك كتابة شيفرة واحدة واستعمالها مع أكثر من مشروع لما يُوفّره لنا مُحرّك القوالب Jinja من مرونة في التّطوير وقابليّة إعادة استعمال الشّفرة في مُختلف الظّروف. في هذا الدّرس، سنتعرّف على السّياق (Context) في Jinja عند التّضمين والاستيراد لتتفادى الوقوع في أخطاء يصعب تصحيحها. لتطبيق أمثلة هذا الدّرس، يُمكنك إنشاء تطبيق Flask صغير ليقوم بتقديم قوالب HTML أو يُمكنك استعمال إطار عمل آخر مثل Pyramid أو Django أو Bottle لكن سيتوجّب عليك البحث عن كيفيّة استعمال Jinja مع إطار العمل الذي تختاره. السّياق في مُحرّك القوالب Jinja عند تضمين قالب في قالب آخر أو عند الاستيراد يعني مُختلف المُتغيّرات والدّوال المُعرّفة في جزء مُعيّن من الشّفرة. فمثلا، إن عرّفت مُتغيّرا في الجزء العلوي من قالب HTML كما يلي: {% set website = 'academy.hsoub.com' %} بعد تعريف المُتغيّر website في القالب، فسيُصبح هذا المُتغيّر مُتاحا في هذا القالب فقط، ما يعني بأنّك لن تستطيع الوصول إلى المُتغيّر website في قالب HTML آخر دون استيراده. السّياق عند تضمين قالب داخل قالب آخر فلنضع السّيناريو التّاليَ لتتوضّح الفكرة أكثر، لنقل بأنّ لدينا ملفّات HTML التّالية: base.html index.html url.html مُحتويات ملفّ base.html ستكون كالتّالي: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <link href="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"> <title>{% block title %}{% endblock %}</title> </head> <body> {% block body %} {% endblock %} </body> </html> وملفّ index.html يرث من الملفّ base.html مع تعريف المُتغيّر website وتضمين الملفّ url.html باستعمال الجملة include: {% extends 'base.html' %} {% block title %} My Application {% endblock %} {% block body %} {% set website = 'academy.hsoub.com' %} {% include 'url.html' %} {% endblock %} لنقل بأنّك تُريد الوصول إلى قيمة المُتغيّر website داخل الملفّ url.html لعرض الرّابط داخل وسم <a> كما يلي: {# 'url.html' #} <a href='http://{{ website }}'>{{ website }}</a> ستكون النّتيجة كما يلي: <a href='http://academy.hsoub.com'>academy.hsoub.com</a> نجح الأمر لأنّ السّياق قد مُرِّر من الملفّ index.html إلى الملفّ url.html عند استخدام التعليمة include، ما يعني بأنّ المُتغيّرات المُعرّفة في الملفّ الأصلي ستكون مُتاحة في الملفّ الفرعي الّذي ضُمِّن بالجملة include. لكنّك لن تستطيع الوصول إلّا إلى المُتغيّرات التي عُرّفت قبل استعمال الجملة include. ما يعني بأنّك لو عرّفت المُتغيّر website بعد تضمين القالب url.html كما يلي: {% include 'url.html' %} {% set website = 'academy.hsoub.com' %} فالقالب url.html لن يتعرّف على المُتغيّر website وسيعتبره سلسلة نصيّة فارغة لأنّه لا يستطيع الوصول إلى قيمته. ستحصل بالتّالي على النّتيجة التّالية: <a href='http://'></a> لاحظ بأنّ مُحرّك القوالب Jinja لم يُصدر أية أخطاء، لذا كن حذرا من هذا النّوع من المشاكل الصّامتة، وتأكّد دائما من الاطّلاع على مصدر الصّفحة لتفهم ما يحدث وتستطيع حلّ المُشكلة. تضمين قالب داخل قالب آخر باستعمال التعليمة include يُمرّر السّياق مبدئيا من قالب لآخر ، لكن إن أردت منع حدوث هذا الأمر، يُمكنك استعمال التّعبير without context لمنع القالب الفرعي من الوصول إلى سيّاق القالب الأصلي، وبالتّالي لن يتمكّن من الوصول إلى المُتغيّرات التّي سبق تعريفها. لتجربة هذا، أضف التّعبير without context إلى صفحة index.html ليُصبح مُحتوى الملفّ كما يلي: {% extends 'base.html' %} {% block title %} My Application {% endblock %} {% block body %} {% set website = 'academy.hsoub.com' %} {% include 'url.html' without context %} {% endblock %} إن ألقيت نظرة على النّتيجة، ستجدها مُشابهة لما كانت عليه عندما عرّفنا المُتغيّر website بعد التعليمة include ما يعني أنّ القالب url.html لم يعد مُؤهّلا للوصول إلى قيمة المُتغيّر، ولهذا نفس التّأثير مع جميع المُتغيّرات والدّوال المُعرّفة في السّياق. السّياق عند الاستيراد يُمرّر السّياق مبدئيا بين القوالب عند استعمال الجملة include، لكنّ العكس يحدث عند استيراد قالب داخل قالب آخر. لتوضيح هذه الفكرة، لنُغيّر مُحتويات الملفّ url.html إلى الماكرو التّالي : {% macro url() %} <a href='http://{{ website }}'>{{ website }}</a> {% endmacro %} لاحظ بأنّ الماكرو لا يقبل أية مُعاملات، لكنّنا نحاول أن نصل إلى المُتغيّر website داخل الماكرو. لنُغيّر الآن التعليمة include إلى جملة import لاستيراد هذا الماكرو من القالب url.hmtl: {% set website = 'academy.hsoub.com' %} {% from 'url.html' import url %} {{ url() }} لاحظ بأنّنا نقوم بالاستيراد بعد تعريف المُتغيّر. بما أنّ السّياق لا يُمرّر مبدئيا عند الاستيراد، فالنّتيجة ستكون كما لو أنّ قيمة المُتغيّر website فارغة. استعمل التّعبير with context عند الاستيراد كما يلي، للتصريح برغبتك في تمرير السّياق : {% from 'url.html' import url with context %} التّعبير with context له تأثير مُعاكس لتأثير التّعبير without context، لذا فالمُتغيّر website سيُمرَّرُ إلى القالب url.html وبالتّالي فالماكرو url() يستطيع الوصول إلى قيمة المُتغيّر ومن ثمّ عرضه بصيغة رابط. ملحوظة: المثال أعلاه مُجرّد توضيح لفكرة السّياق عند الاستيراد، ومن الأفضل استعمال المُعاملات لنقل قيمة مُتغيّر إلى ماكرو مُعيّن عوضا عن تفعيل السّياق. الخلاصة أنّك تستطيع التّحكم في كيفيّة تعامل القوالب مع السّياق عند التضمين أو الاستيراد عبر استخدام كلّ من التّعبيرين with context و without context. الحالة المبدئية للتعليمة include هي كالتّالي: {% include 'url.html' with context %} والحالة االمبدئية للتعليمة import: {% import 'url.html' as url without context %} تجاهل تضمين القوالب إن لم تكن متواجدة أساسا سأعرض لك في هذه الفقرة بشكل وجيز تقنيتين من التّقنيات التّي ستُساعدك على تصميم قوالب HTML أفضل عند استخدام التعليمة include. أولا، يُمكنك منح مُحرّك القوالب Jinja إمكانيّة تضمين قالب فقط إن كان موجودا عبر تمرير قائمة من أسماء القوالب إلى التعليمة include كالتّالي: {% include ['page1.html', 'page2.html'] %} إذا كان القالب page1.html موجودا فسيكون للمثال أعلاه نفس تأثير ما يلي: {% include 'page1.html' %} سواء أكان القالب page2.html موجودا أم لا فهذا غير مُهمّ ما دام القالب page1.html أول قالب في القائمة وكان موجودا بالفعل داخل مُجلّد templates. أمّا في حالة لم يكن القالب page1.html موجودا داخل مُجلّد القوالب وكان القالب page2.html موجودا عوضا عنه، فهذا الأخير هو القالب الذي سيُضمّن. وفي حالة لم يكن أي قالب من القوالب المُوفَّرة موجودا، فستحصل على خطأ كالتّالي: jinja2.exceptions.TemplatesNotFound TemplatesNotFound: none of the templates given were found: page1.html, page2.html ولتجاهل هذا الخطأ وعرض مُحتويات القالب index.html رغم عدم وجود هذه القوالب المُضمَّنة، يُمكنك إضافة التّعبير ignore missing عند استعمال التعليمة include كالآتي: {% include ['page1.html', 'page2.html'] ignore missing %} التّعبير ignore missing ليس محصورا بقائمة قوالب فقط، بل تستطيع كذلك استخدامه عند تضمين قالب واحد فقط كما يلي: {% include "sidebar.html" ignore missing %} عند استعمال التّعبير، سيتحقّق مُحرّك القوالب Jinja من أنّ القالب sidebar.html موجود في مُجلّد القوالب، إن كان كذلك فعمليّة التّضمين ستكون ناجحة دون أخطاء، وإن لم يكن القالب موجودا فلن تحدث أية أخطاء وسيبقى مكان التعليمة include فارغا عند عرض النّتيجة. تنبيه: عند استخدام التّعبير ignore missing مع كلّ من التّعبيرين with context و without context في آن واحد، تأكّد من أنّ التّعبير ignore missing يأتي أولا. ما يعني بأنّ الأمثلة أسفله صحيحة: {% include ['page1.html', 'page2.html'] ignore missing with context %} {% include "sidebar.html" ignore missing with context %} {% include "sidebar.html" ignore missing without context %} والأمثلة التّاليّة خاطئة: {% include ['page1.html', 'page2.html'] with context ignore missing %} {% include "sidebar.html" with context ignore missing %} {% include "sidebar.html" without context ignore missing %} خاتمة تعرّفنا في هذا الدّرس على كيفيّة عمل السّياق في Jinja عند التّضمين والاستيراد، بعد قراءتك لهذا الدّرس، يجب أن تكون قادرا على إدارة السّياق باحترافيّة وتتجنّب المشاكل التي تقع في حالة إساءة فهم كيفيّة عمل السّياق، في الدّرس التّالي، سنتعرّف على تقنيات متقدّمة للتّعامل مع حلقات for من أجل تحسين تطبيقاتك وتكتب شفرة أكثر فعاليّة عند تطوير تطبيقات الويب باستعمال إطار العمل Flask أو أي إطار عمل يعتمد على Jinja.
-
مُقدّمة تعرّفنا في الدّرس السّابق على التعليمة include وكيفيّة استخدامها في مُحرّك Jinja لتضمين قالب HTML داخل قالب آخر، في هذا الدّرس سنتعرّف على ميّزة أخرى ستُساعدك على تفادي تكرار الشّيفرة، إذ سنتعرّف على تعليمة import لاستيراد مكوّن ما من قالب واستعماله في قالب آخر. ما الغرض من الاستيراد؟ تعرّفنا مُسبقا على خاصّية الماكرو في Jinja، وقلنا بأنّها مُفيدة لتفادي تكرار الشّفرة وأنّها تُستخدم كما تُستخدم الدّوال في لغة بايثون، لكنّنا لم نتطرّق بالتّفصيل إلى جزئيّة مُهمّة، ألا وهي كيفيّة جمع عدّة دوال في قالب واحد ليعمل مثل الوحدة (Module) في بايثون، بحيث يشمل عدّة دوال يُمكنك استيرادها بسطر واحد واستخدامها في أكثر من قالب واحد عوضا عن كتابة الماكرو أعلى الصّفحة. يُمكنك كذلك استخدام قالب HTML مُعيّن كمخزن لإعدادات تطبيق، مثل اسم التّطبيق والمُعاملات التّي يُمكنك بها التّحكّم في جزئيّات مُعيّنة ممّا يُعرض على مُتصفّح المُستخدم، ومن ثمّ يُمكنك استيراد المُعاملات من هذا القالب واستغلالها في بقيّة القوالب كما تشاء لتوفير ديناميكيّة أفضل لتطبيقك. التعليمة import لاستيراد مُتغيّر أو ماكرو من قالب مُعيّن كتابة شيفرة نماذج HTML أمر مُملّ، لذا قد ترغب في كتابة بعض الدّوال لتوليد النّماذج بشكل سريع اعتمادا على مُعاملات مُعيّنة. لنفترض بأنّ لدينا ما يلي في مُجلّد templates الخاصّ بتطبيق Flask: ملفّ base.html . ملفّ index.html للصّفحة الرّئيسية. ملفّ باسم forms.html. سيحتوي ملفّ base.html على شفرة HTML بسيطة: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <link href="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"> <title>{% block title %}{% endblock %}</title> </head> <body> {% block body %} {% endblock %} </body> </html> أمّا ملفّ index.html فسيرث من الملفّ السّابق بالإضافة إلى عنوان داخل وسم <h1> لطلب تسجيل الدّخول من المُستخدم: {% extends 'base.html' %} {% block title %} My Application {% endblock %} {% block body %} <div class="container"> <h1>Please Login</h1> </div> {% endblock %} أما forms.html فسيحتوي على ماكرو خاصّ بعرض النّماذج من نوع input و ماكرو آخر لعرض نموذج لمساحة النّص (TextArea): {% macro input(name, value='', type='text', placeholder='') %} <input class="form-control" type="{{ type }}" value="{{ value|e }}" name="{{ name }}" placeholder="{{ placeholder }}"> {% endmacro %} {% macro textarea(name, value='', rows=10, cols=40, placeholder='') %} <textarea class="form-control" name="{{ name }}" rows="{{ rows }}" cols="{{ cols }}" placeholder="{{ placeholder }}">{{ value|e }}</textarea> {% endmacro %} ينشئ الماكرو الأول حقل إدخال من نوع input، يقبل الماكرو ثلاثة مُعاملات: name لاسم الحقل. value لقيمة الحقل المبدئية. type لنوع الحقل (نصّ عادي، كلمة مرور، …إلخ). placeholder للنّص المبدئي. تُستعمل هذه المُعاملات إن وفّرها المُطوّر للتّحكم بكيفيّة توليد الحقل، فمثلا القيمة المبدئية للحقل type هي text ما يعني بأنّك إن لم تُمرّر قيمةً لهذا المُعامل فسيُولّد الماكرو حقلا نصّيا عاديّا، ولو غيّرت القيمة مثلا إلى password فسيكون الحقل المُولّد عبارة عن حقل كلمة مرور. قد تُلاحظ بأنّنا نستخدم المُرشّح e الذي هو اختصار لكلمة escape والذي يقوم بحماية الحقل من الهجمات الخبيثة عبر منع ترجمة شفرات HTML وJavascript. أمّا بالنّسبة للماكرو textarea فهو مُشابه لما سبق، إلّا أنّه يُولّد وسما من النّوع textareaالذي يُعبّر عن حقل لنصّ مُتعدّد الأسطر، كما أنّه يقبل كلّا من المُعاملين rows و cols لتحديد حجم مساحة النّص التي ستظهر في صفحة HTML. استعمال التعليمة import لاستيراد الوحدة forms.html بعد أن أنشأنا قاعدة لمثالنا، وبعد أن كتبنا دوال خاصّة بتوليد حقول الإدخال وجمعها في ملفّ forms.html لتُمثّل وحدة كما في وحدات Python، لنستعملها في ملفّ index.html. أولا، ضع سطر الاستيراد في رأس صفحة index.html: {% import 'forms.html' as forms %} لاحظ بأنّنا استعملنا الكلمة المفتاحية as لإسناد اسم مُستعار للوحدة المُستوردة بحيث نتمكّن من الوصول إليها عبر الاسم forms. بعد استيراد الوحدة، يُمكنك استدعاء الدّوال المُتواجدة بداخلها كما يلي: forms.input() forms.textarea() لكن ستحتاج إلى تمرير مُعاملات حسب نوع الحقل الذي ترغب بتوليده. لنُضف الآن بضعة حقول إلى ملفّ index.html باستخدام الوحدة التّي قمنا باستيرادها: {{ forms.input('username', placeholder='Type in your username') }} <br> {{ forms.input('password', placeholder='Type in your password', type='password') }} <br> {{ forms.textarea('message', placeholder='Write your message here.') }} <br> <button class="btn btn-primary">Submit</button> في الشّيفرة أعلاه، نستعمل الماكرو forms.input لإنشاء حقل نصي لاسم المُستخدم، ونستخدمه مُجدّدا لإنشاء حقل لكلمة المرور الذي حدّدنا نوعه بالقيمة password، ثمّ بعد ذلك أنشأنا حقلا لمساحة النّص مع تعيين القيمة message كاسم للحقل، وفي آخر سطر أضفنا زرّا صغيرا لإرسال مُحتويات النّموذج. بعد إضافة الشّفرة السّابقة، ستكون مُحتويات الملفّ index.html كما يلي: {% import 'forms.html' as forms %} {% extends 'base.html' %} {% block title %} My Application {% endblock %} {% block body %} <div class="container"> <h1>Please Login</h1> {{ forms.input('username', placeholder='Type in your username') }} <br> {{ forms.input('password', placeholder='Type in your password', type='password') }} <br> {{ forms.textarea('message', placeholder='Write your message here.') }} <br> <button class="btn btn-primary">Submit</button> </div> {% endblock %} والصّفحة ستكون مُشابهة للمثال في الصّورة التّالية: مُلاحظة: الغرض من هذه الأمثلة هو شرح مبدأ الاستيراد وكيفية عمله في مُحرّك القوالب Jinja. إن أردتَ إدارة نماذج HTML على نحو أفضل وأكثر أمانا، فمن الأفضل استخدام مكتبة WTForms. استيراد مكوّن مُعيّن باستخدام التّعبير from import بالإضافة إلى إمكانيّة استيراد وحدة واستعمال الدّوال المتواجدة فيها كما يلي: import module module.macro() يُمكنك كذلك استخدام التّعبير from import لاستيراد ماكرو واحد فقط كما في المثال التّالي: {% from 'forms.html' import input %} هكذا ستتمكّن من استعمال الماكرو input عبر استدعائه، لكنّك لن تستطيع الوصول إلى الماكرو textarea حتى تستدعيه على نحو صريح. لاستدعاء أكثر من ماكرو واحد، افصل بينها بفاصلة كالتّالي: {% from 'forms.html' import input, textarea %} يُمكنك كذلك استعمال الكلمة المفتاحية as لمنح اسم مُستعار لكلّ ماكرو: {% from 'forms.html' import input as i, textarea as t %} بعد استيراد الماكرو باسم مُستعار، يُمكنك استخدام هذا الاسم المُستعار كما يلي: {{ i() }} {{ t() }} خاتمة بنهاية هذا الدّرس، يجب أن تكون قادرا على تصميم قوالب HTML بمرونة أكثر، بحيث تتمكّن من استغلال الجملة import لتفادي تكرار الشّيفرة والحصول على تجربة تطوير أفضل.
-
مُقدّمة تكرار الشّيفرة من أسوء المُمارسات التّي يُمكن لأي مُطوّر القيام بها، إذ تستهلك الكثير من الوقت الثّمين الّذي يُمكن صرفه في تطوير ميّزات أخرى وأمور أكثر أهميّة؛ سنتعرّف في هذا الدّرس على كيفيّة استغلال مُحرّك القوالب Jinja لتفادي تكرار الشّيفرة وتقسيم ملفّ HTML كبير إلى عدّة ملفّات صغيرة يُمكن إعادة استعمالها عبر الاعتماد على التعليمة include في Jinja. التعليمة include لتضمين قالب HTML داخل آخر تُستعمل التعليمة include لتضمين مُحتويات ملفّ HTML في مكان ما في قالب HTML آخر. على سبيل المثال، لنقل بأنّ لدينا خمسة قوالب HTML في مجلّد templates الخاصّ بنا كما يلي: ملفّ باسم base.html ملفّ باسم header.html ملفّ آخر باسم content.html ملفّ آخر باسم footer.html الملفّ الرّئيسي باسم main.html هكذا ستكون الصّورة العامّة للملفّات داخل المُجلّد templates كما يلي: └── templates ├── body.html ├── footer.html ├── header.html └── main.html └── base.html بالنّسبة للقالب base.html فهو معروف وطريقة استعماله واضحة، إذ يُعتبر القاعدة الأساسيّة لصفحات HTML التّي تعتمد عليه، ومُكوّناته ستكون كالتّالي: <!DOCTYPE html> <html lang="en"> <head> <link href="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"> <meta charset="UTF-8"> <title>{% block title %}{% endblock %}</title> </head> <body> {% block body %} {% endblock %} </body> </html> كما تُلاحظ هنا فإنّنا نقوم بوضع شِفرة HTML التّي تتكرّر في كلّ صفحة وفيها جزءان ديناميكيّان، الجزء الأول يُعنى بعنوان الصّفحة ويوجد داخل الوسم <title> وأمّا الجزء الثّاني فمسؤول عن الظّهور داخل جسم الصّفحة بالوسم <body>، وسنرى كيفيّة استغلال هذا القالب بعد لحظات. لاحظ كذلك بأنّنا أضفنا ملفّ CSS الخاص بإطار العمل Bootstrap في وسم <link> في الشّفرة أعلاه. أمّا عن قالب header.html فسيحمل شِفرة HTML التّي تكون عادة داخل الوسم <header> والتّي تكون مسؤولة عن الجزء العلوي من صفحة HTML كشريط التّصفّح وشعار التّطبيق وغير ذلك من المُكونات التّي تكون أعلى صفحات الويب. شيفرة هذا الملفّ ستكون عادة مُشابهة لما يلي: <header> <div class="navbar navbar-default"> <div class="container-fluid"> <ul class="nav navbar-nav"> <li><a class="navbar-brand" href="#"> Home </a></li> </ul> <ul class="nav navbar-nav navbar-right"> <li><a href="/about/"> <span class="glyphicon glyphicon-info-sign"></span> About </a></li> <li><a href="/contact/"> <span class="glyphicon glyphicon-envelope"></span> Contact </a></li> </ul> </div> </div> </header> إن لم تفهم الشّفرة أعلاه فلا تقلق، إذ سيتّضح كلّ شيء بعد أن نجمع الملفّات بعضها مع بعض. لكن عليك أن تفهم فقط بأنّ الشّفرة بالأعلى عبارة عن شفرة HTML مع تنسيقات من إطار العمل Bootstrap تُمثّل شريط تصفّح به ثلاثة أزرار، الزّر Home للرّبط بالصّفحة الرّئيسية، والزّر About لصفحة “عن التّطبيق” والزّر Contact لصفحة التّواصل. أمّا ملفّ footer.html فسيحمل الشّفرة التّي تكون عادة أسفل صفحات الوِب مثل حقوق المُحتوى ومعلومات عن الشّركة صاحبة الموقع أو عن فريق التّطوير الّذي ساهم في بناء التّطبيق، بالإضافة إلى إمكانيّة احتوائه على معلومات التّواصل مع أصحاب التّطبيق أو معلومات أخرى، وقد تُلاحظ أسفل هذا الموقع (موقع أكاديميّة حسوب)، جزءا خاصّا بمُنتجات شركة حسوب وقد تُلاحظ ذلك في الصّفحات الأخرى التّابعة لمُنتجات حسوب، وهذا ما أقصده بالضّبط. كمثال على مُحتوى هذا الملفّ، سنستعمل الشِّفرة التّاليّة لعرض حقوق الموقع واسم المُطوّر: <footer class="footer"> <div class="container"> <p>Built by Abdelhadi Dyouri</p> <p class="text-muted">All rights reserved.</p> </div> </footer> بالنّسبة للملفّ content.html، فسيكون المسؤول عمّا بداخل الوسم <div> ذي خاصيّة CSS المسمّاة container التّي يُوفّرها إطار العمل Bootstrap. سيشمل هذا الجزء المُكونات التي تُمثّل المُحتوى العام للموقع، فمثلا إن كان الموقع معنيّا بنشر المقالات، فالمُحتوى الّذي سيكون بداخل هذا الوسم هو لائحة المقالات وإن كانت الصّفحة خاصّة بعرض مقال واحد، فعنوان هذا المقال ومُحتواه وكاتبه والمعلومات العامّة حول المقال ستكون داخل هذا الوسم. ما يُميّز هذا الجزء من صفحات HTML هو أنّه يكون مُتغيّرا عكس كلّ من رأس الصّفحة وأسفلها، إذ يبقى مثلا شريط التّصفّح هو نفسه في جميع صفحات التّطبيق وكذلك مُكوّنات أسفل الصّفحة كالحقوق والمعلومات حول التّطبيق، إذ يُمكن للمقالات في وسط الصّفحة أن تتبدّل وأن يتغيّر نوع المحتوى من مقال إلى صفحة لاستعراض المعلومات الشّخصيّة للكاتب وما إلى ذلك. على سبيل المثال، سنستعمل المثال الّذي سبق وأن شرحناه في درس الدّالتين range وlipsum لملْء مُحتوى هذا الملفّ: <div class='container'> {% for i in range(8) %} <div class='col-md-3'> <h2> <a href='#'> {{ lipsum(1, min=3, max=5) }} </a> </h2> {{ lipsum(1, max=50) }} <button class='btn btn-default'>Read more...</button> </div> {% endfor %} </div> إن لم تقرأ الدّرس من قبل، فلا تقلق، المهم أن تفهم بأنّ الشّفرة أعلاه تُنتج ثمانيّة فقرات مع عنوان باستعمال نصّ lorem ipsum، ما يُتيح لنا الحصول على مُحتوى يُشبه المقالات دون كتابة شيفرة HTML كبيرة ومُكرّرة مكان استعمال دوال في Jinja للقيام بالأمر بكلّ بساطة. بالنّسبة للملفّ main.html، فهو الملفّ الرّئيسي الّذي سنستعمله لجمع هذه الملفّات لتظهر على شكل صفحة واحدة. الشّيء الّذي سنقوم به في الفقرة التّالية. جمع ملفّات HTML لتظهر في صفحة واحدة باستخدام التعليمة include بعد أن أصبح لدينا كلّ من الجزء العلوي للموقع في ملفّ header.html ومُحتوى الموقع في الملفّ content.html والجزء السّفلي للصّفحة داخل ملفّ footer.html، حان الوقت لجمع كلّ هذه المُكونات في ملفّ واحد لتجتمع في صفحة واحدة. سنستخدم الملفّ main.html لجمع جميع هذه المُكوّنات وسنستعمل التعليمة include كما شرحنا مُسبقا: {% extends 'base.html' %} {% block title %} My Application {% endblock %} {% block body %} {% include 'header.html' %} {% include 'content.html' %} {% include 'footer.html' %} {% endblock %} كما تُلاحظ، فقد استعملنا التعليمة extends للوراثة من الملفّ base.html الذي يُعتبر قاعدة الصّفحة، بعدها حدّدنا عنوانا للصّفحة عن طريق السّطر {% block title %} My Application {% endblock %}، بعدها حدّدنا جسم الصّفحة داخل الجزء body ومن ثمّ استعملنا التعليمة include لتضمين كلّ من الملفّات header.html، content.html وfooter.html داخل الصّفحة، لكن لاحظ بأنّ التّرتيب مُهمّ وهو نفس التّرتيب المنطقي الذي يجب اتّباعه، بحيث يكون تضمين الجزء العلوي أولا ثمّ الجزء المسؤول عن المُحتوى ثمّ الجزء السّفلي للصّفحة. يُمكنك أن تتخيّل بأنّ الجملة include غير مُتواجدة ويتواجد في مكانها كلّ الشّيفرة المتواجدة داخل ملفّ HTML المُضمّن، فعوضا عن السّطر {% include 'header.html' %} ستكون شيفرة الملفّ header.html وهكذا… بعد إنهاء تطبيق ما سبق، ستُلاحظ نتيجة مُشابهة لما في الصّورة التّاليّة: متى تستعمل التعليمة include ولماذا استعمال التعليمة include مُفيد لأنّه يُعطيك حرّية أكثر في العمل على أجزاء مُختلفة من صفحات الويب في تطبيقك، إذ يُمكنك تطوير الجزء السّفلي من الصّفحة دون الاضطرار إلى المرور بالشّفرة المسؤولة عن الأجزاء الأخرى، ما يُتيح لك تجربة تطوير أفضل، بالإضافة إلى أنّ تقسيم أجزاء الصّفحات إلى ملفّات HTML مُستقلّة أمر مُفيد إن كنت تعمل مع أحدهم على تطوير نفس التّطبيق أو حتّى فريق مُكوّن من أكثر من شخصين، إذ يُمكنك مثلا العمل على الجزء العلوي من الصّفحة وتترك صديقك يعمل على مُحتويات الصّفحة وصديق آخر يعمل على الجزء السّفلي بحيث لا يُعدّل الجميع نفس الملفّ في نفس الوقت. بالإضافة إلى أنّ التعليمة include تُساعد على تقسيم ملفّ HTML إلى عدّة ملفّات صغيرة لتسهيل عمليّة التّطوير، يُمكن كذلك أن تستفيد من هذه الملفّات عبر إعادة استعمالها في أكثر من ملفّ HTML لتفادي تكرار الشّيفرة واختصار الوقت. خاتمة تعرّفنا في هذا الدّرس على كيفيّة تقسيم صفحة HTML إلى عدّة ملفّات وكيفيّة جمعها داخل ملفّ واحد باستعمال الجملة include لتوفير تجربة تطوير أفضل، في الدّرس القادم، سنلقي نظرة على مبدأ الاستيرادات في مُحرّك القوالب Jinja وكيفيّة الاستفادة منه.
-
تمهيد إلى الآن، تعرّفنا على العديد من أساسيّات تطوير الويب باستعمال كلّ من إطار العمل Flask ومُحرّك القوالب Jinja، وفي الدّرس السّابق، تعرّفنا على ماهيّة الدّوال المُسبقة التّعريف في هذا الأخير، إذ تعرّفنا كبداية على كلّ من الدّالة range() والدّالة lipsum، وسنُكمل في هذا الدّرس ما بدأناه لنتعرّف على بقيّة الدّوال والأصناف المبنيّة مُسبقا في مُحرّك القوالب Jinja ليُساعدك ذلك على تطوير تطبيقات وِب أفضل. الدّالة dict في لغة بايثون، هناك نوع خاصّ من القيم يُسمّى القواميس Dictinaries، في كلّ قاموس نجد مفاتيح وقيّما لتُساعدنا على ترتيب البيانات وتسهيل البحث عن قيمة ما عبر مفتاحها، كما نستطيع الوصول إلى كلّ من القيم والمفاتيح باستعمال حلقة for بسيطة. في مُحرّك القوالب Jinja، القواميس مُشابهة لقواميس بايثون، إذ يُمكنك تعريف قاموس كما يلي: {% set dictionary = {'1': 'One', '2': 'Two'} %} في المثال أعلاه، كلّ من 1 و 2 عبارة عن مفاتيح، أمّا One و Two فهي قيم المفاتيح، بحيث One قيمة المفتاح 1 و Two قيمة المفتاح 2. للحصول على القيم في القاموس، يُمكن استخدام اسم المُتغيّر الذي يحمل القاموس مع المفاتيح كما يلي: {{ dictionary['1'] }} <br> {{ dictionary['2'] }} النّتيجة: One Two مُلاحظة: في المثال أعلاه، قُمنا باستخدام الكلمة المفتاحيّة set لتعريف مُتغيّر يحمل قاموسا كقيمة، لكن يُمكن كذلك أن تُمرّر مُتغيّرا يحمل قاموسا من ملفّات التّحكم – وهو الجزء الّذي يهتمّ به إطار العمل Flask - إلى قوالب HTML، لذا ففي هذه الحالة ستتمكّن من الوصول إلى القاموس مُباشرة من المُتغيّر دون تعريفه على نحو صريح باستعمال مُحرّك القوالب Jinja. يُمكنك في مُحرّك القوالب Jinja إنشاء قواميس بطريقة أفضل عبر الاستعانة بالدّالة dict()، إذ يُمكنك استعمال الدّالة لإنشاء قاموس تكون مفاتيحه هي أسماء المُعاملات وقيمة المُعامل هي نفسها قيمة المفتاح. التّالي مثال على كيفيّة استخدام الدّالة dict() لإنشاء قاموس بسيط: dict(key='value', another_key='another value') المثال أعلاه سيُنتج قاموسا كما يلي: {'another_key': 'another value', 'key': 'value'} تُستخدم القواميس عندما يكون لدينا نوع من القيم المُترابطة والتّي لها أصل واحد، فمثلا لنقل بأنّ القاموس يُمثّل بطاقة تعريف أحدهم، في القاموس سيتوجّب علينا تخزين الاسم الأوّل للشّخص، والاسم العائلي أو نسبُ نفس الشّخص. المثال التّالي توضيح على كيفيّة استخدام الدّالة dict لإنشاء قاموس يعمل على تخزين الاسم الكامل لشخص ما وعرض جملة ترحيبيّة مُخصّصة له: {% set dictionary = dict(firstname='أحمد', lastname='علي') %} مرحبا يا {{ dictionary['firstname'] }} {{ dictionary['lastname'] }}! النّتيجة ستكون الجملة التّاليّة: مرحبا يا أحمد علي! وبالطّبع فالنّتيجة ستتغيّر كلّما غيّرت أحد قيمتي المُعاملين firstname وlastname أثناء استدعاء الدّالة dict. من الأمور المُميّزة في الدّالة dict أنّها تُعطيك الحرّية في اختيار كيفيّة الوصول إلى قيمة كلّ مفتاح. سابقا، استعملنا الطّريقة التّقليديّة للوصول إلى قيم القاموس كما يلي: dictionary['firstname'] dictionary['lastname'] لكن يُمكننا كذلك الوصول إلى القيم بطريقة أسرع كما يلي: dictionary.firstname dictionary.lastname بهذه الطّريقة يُمكنك اعتبار المفاتيح خصائص كما في خصائص الأصناف في لغة بايثون، ما يعني بأنّك تستطيع استخدام المُرشّحات والاختبارات التّي تسمح لك باختبار قيم الخصائص مُباشرة. بالتّالي فالمثال السّابق سيكون كما يلي: مرحبا يا {{ dictionary.firstname }} {{ dictionary.lastname }}! ستُلاحظ بأنّ النّتيجة هي نفسها وبأنّ هذا المثال الأخير أفضل من المثال السّابق لأنّك تستطيع اختصار بعض الوقت عند الوصول إلى قيم المفاتيح بهذه الطّريقة. الصّنف cycler يُمكّنك الصّنف cycler من الدّوران حول مجموعة من العناصر بشكل لا نهائي، مع إمكانيّة إعادة الدّوران في وقت مُحدّد إن أردت ذلك. على سبيل المثال، لنقل بأنّك تُريد الدّوران حول قائمة من الأسماء بشكل لا نهائي بحيث تُعاد الدّورة في كلّ مرّة. لنضع الشّيفرة التّالية كمثال لكيفيّة استعمال الصّنف cycler: {% set names = cycler('Ahmed', 'Ali', 'Hassan') %} في هذا المثال، أنشأنا كائنا من الصّنف cycler وأسندناه إلى المُتغيّر names. يُمكننا الآن استخدام الكائن names للدّوران حول قائمة الأسماء التّي مرّرناها إلى الصّنف cycler. من المُهم أن نفهم بأنّ الكائن names سيحمل قيمة واحدة فقط في كلّ مرّة نستدعيه فيه باستخدام أحد توابع أو خصائص الصّنف cycler، إذ يُمكننا استخدام الخاصيّة current للحصول على القيمة الحاليّة، والتّابع next() للوصول إلى القيمة التّاليّة، بالإضافة إلى التّابع reset() لإعادة الدّوران من جديد والوصول إلى أول قيمة. القيمة التي سيحملها الكائن names عند استعمال أحد التّوابع ستكون أحد الأسماء في مجموعة الأسماء التّي حدّدناها من قبل فقط ولا يُمكن أن تكون قيمة أخرى. الوصول إلى قيمة الدّوران الحاليّة إن اعتبرنا المثال السّابق، فإنّنا نستطيع الوصول إلى قيمة الدّوران الحاليّة كما يلي: {% set names = cycler('Ahmed', 'Ali', 'Hassan') %} {{ names.current }} وبما أنّنا لم نقم بأي انتقال من قيمة إلى الّتي تليها عبر التّابع next()، فالقيمة البدئيّة ستكون أول عنصر من مجموعة العناصر المُمرّرة إلى الصّنف cycler، أي الاسم Ahmed في هذه الحالة. الوصول إلى قيمة الدّوران التّاليّة للوصول إلى القيمة التّاليّة وعرضها، يُمكنك استخدام التّابع next() على الكائن names كما يلي: {{ names.next() }} ستُلاحظ بأنّ النّتيجة هي نفسها النّتيجة التّي تظهر عند استخدام الخاصيّة current أي أنّ القيمة التّي ظهرت هي العنصر الأول من قائمة العناصر. هذا لأنّه يتوجّب علينا استخدام التّابع next() أكثر من مرّة ليظهر مفعوله: {{ names.next() }} <br> {{ names.next() }} <br> {{ names.next() }} <br> {{ names.next() }} <br> {{ names.next() }} <br> استخدمنا في المثال أعلاه التّابع next() خمس مرّات، والنّتيجة كما المُتوقّع ستكون كالتّالي: Ahmed Ali Hassan Ahmed Ali كما تُلاحظ، عندما وصلت القيمة إلى آخر عنصر (أي الاسم Hassan)، فقد عادت الدّورة إلى البداية من جديد لتستمرّ في الدّوران حول القيم، هذا يعني بأنّك تستطيع استخدام التّابع next() لعدد لا نهائي من المرّات مع أي مجموعة من العناصر. استخدام الصّنف cycler مع قائمة بايثون بما أنّ الصّنف cycler يعمل مع قيمٍ عدّة، فمن الطّبيعي أن ترغب في استخدامه مع قائمة عاديّة تحتوي على مجموعة من العناصر. لكن إن مرّرت قائمة إلى الصّنف cylcer كما يلي: set names = cycler(['Ahmed', 'Ali', 'Hassan']) فسيعتبر الصّنف هذه القائمة المُمرّرة مُجرّد قيمة واحدة، ولو استعملت مثلا التّابع next() على القائمة مرّتين فستحصل على النّتيجة التّالية: ['Ahmed', 'Ali', 'Hassan'] ['Ahmed', 'Ali', 'Hassan'] هذا يعني بأنّ الصّنف cycler لا يعتبر القائمة مجموعة من العناصر كما يجب. لحلّ هذه المُشكلة، استعمل الرّمز * قبل القائمة لتفكيكها وتمرير كلّ عنصر منها على شكل مُعامل كما يلي: set names = cycler(*['Ahmed', 'Ali', 'Hassan']) هذه الطّريقة تعمل حتى لو كانت القائمة مُسندة إلى مُتغيّر ما كذلك، على سبيل المثال إن كانت لديك قائمة باسم list وأردت تمرير عناصرها إلى الصّنف cycler فيُمكنك ذلك كما يلي: cycler(*list) خاتمة تعرّفنا في هذا الدّرس على كلّ من الدّالة dict والصّنف cycler لمُساعدتك على بناء صفحات HTML أفضل عند استعمال كلّ من إطار العمل Flask ومُحرّك القوالب Jinja، في الدّرس القادم سنتعرّف على جزء آخر لا يقلّ أهميّة عمّا درسناه في الدّروس السّابقة، ألا وهو كيفيّة التّعامل مع أكثر من ملفّ عبر تضمينها بعضها ببعض لتفادي تكرار الشّيفرة البرمجيّة.
-
مُقدّمة بعد أن تعرّفنا على المُرشّحات والاختبارات في مُحرّك القوالب Jinja، بقي أن نتعرّف على الدّوال التّي يُمكنك استعمالها لتسهيل عمليّة تطوير قوالب HTML، هذه الدّوال مُشابهة للدّوال التّي يُمكنك استخدامها في لغة بايثون، ما يعني بأنّك تستطيع استخدامها كما تُستخدم في لغة بايثون، أي باستدعائها وتمرير مُعاملات إليها إن كان ذلك مُمكنا. في هذا الدّرس، سنتعرّف على كلّ من الدّالة range والدّالة lipsum اللّتان تُعتبران دالّتين مُهمّتين لكل مُصمّم قوالب ولمُطوّري الويب الّذين يستخدمون إطار العمل Flask خصّيصا. ولتطبيق الأمثلة المذكورة في هذا الدّرس، يُمكنك الاستعانة بملفّ filters.html الّذي أنشأناه سابقا في بداية هذه السّلسة للتّحقق من نتائج أمثلة المُرشّحات. إن لم تتّبع سلسلة الدّروس منذ البداية، فلا مُشكلة، إذ يُمكنك إنشاء تطبيق بسيط بإطار العمل Flask بحيث يحتوي على موجّه واحد على الأقل يُقدّم صفحة HTML لتستطيع التّعامل معها عبر مُحرّك القوالب Jinja الّذي يُعتبر الأداة الافتراضيّة لإدارة قوالب HTML في تطبيقات الويب المبنيّة بإطار العمل Flask. بعد تجهيز قالب لنختبر به الأمثلة، لننتقل الآن إلى التّعرف على الدّوال المبنيّة مُسبقا في مُحرّك القوالب Jinja. الدّالة range الدّالة range() في Jinja تعمل بشكل مُشابه لطريقة عمل الدّالة التّي تحمل نفس الاسم في لغة Python. إذ تُساعدك على إنشاء قائمة أعداد حسب المُعاملات. افتراضيّا، الدّالة range تعمل إذا ما مرّرت إليها قيمة واحدة كمُعامل عددي، والنّتيجة تكون قائمة بالأعداد من 0 إلى العدد ما قبل العدد الذي مُرِّرَ كمُعامل. مثلا، استدعاء الدّالة range مع تمرير العدد 10 كمُعامل سيكون كما يلي: range(10) نتيجة المثال ستكون كما يلي: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] لاحظ بأنّ النّتيجة عبارة عن قائمة تحتوي على 10 عناصر من الصّفر إلى تسعة دون تضمين العدد 10 الذي يُعتبر المُعامل المُستخدم. مُلاحظة: إن طبّقت المثال أعلاه في ملفّ HTML فلن تظهر النّتيجة حتّى تُطبّق المُرشّح list كما يلي: {{ range(10) | list }} لكن عموما تستطيع استخدام حلقة for مع نتيجة الدّالة مُباشرة دون استخدام المُرشّح list. تحديد القيمة البدئيّة عندما نستعمل الدّالة range في ملفّات HTML مع تمرير مُعامل واحد فقط، فالقيمة البدئيّة تكون دائما العدد 0، لكن يُمكنك تغيير هذا الوضع بتمرير مُعامل قبل مُعامل القيمة القُصوى ليبدأ العدّ من هذه القيمة الجديدة إلى حين الوصول إلى العدد ما قبل القيمة القُصوى. كمثال على ذلك، لنعتبر بأنّنا نحتاج إلى قائمة تبدأ من العدد 5 وتنتهي بالعدد 15، في هذه الحالة سيتوجّب علينا استخدام الدّالة range كما يلي: {{ range(5, 15) | list }} هكذا ستكون النّتيجة كما يلي: [5, 6, 7, 8, 9, 10, 11, 12, 13, 14] كما تُلاحظ، فقد انتهت القائمة بالعدد 14 وليس بالعدد 15 كما قد تتوقّع، لذا لو أردت تضمين العدد 15 في القائمة فسيتوجّب عليك استخدام العدد 16 في المقام الأول كما يلي: range(5, 16) النّتيجة: [5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15] تحديد قيمة الزّيادة افتراضيّا، قيمة الزّيادة هي العدد 1، إذ يكون كلّ عنصر أكبر من العدد الذّي يسبقه في القائمة بواحد، وإن أردت أن تُغيّر قيمة الزّيادة فيُمكنك تمريرها كمُعامل ثالث. على سبيل المثال، لو أردنا الحصول على العشرات التّي تحتوي على رقمين فقط (20, 50 …) فسيُمكننا أن نقوم بتوليد قائمة عناصرها تبدأ من العدد 10 إلى العدد 100 بزيادة قيمتها العدد 10، ويُمكننا القيام بالأمر كما يلي: range(10, 100, 10) النّتيجة ستكون جميع العشرات من 10 إلى 90 في قائمة كالتّالي: [10, 20, 30, 40, 50, 60, 70, 80, 90] يُمكن لقيمة الزّيادة أن تكون سلبيّة كذلك، ما يسمح لك بالحصول على قائمة عكسيّة أو عدّ تنازلي. فمثلا، يُمكننا الحصول على قائمة الأعداد من 10 إلى 1 كما يلي: range(10, 0, -1) هنا نضع العدد 10 كقيمة بدئيّة والعدد 0 كقيمة قُصوى لأنّ آخر عدد قبل الصّفر هو العدد 1، وفي قيمة الزّيادة نضع العدد السّلبي -1. النّتيجة: [10, 9, 8, 7, 6, 5, 4, 3, 2, 1] يُمكنك تغيير قيمة الزّيادة إلى أي عدد سلبي آخر حسب ظروف الاستعمال. أمثلة على استخدام الدّالة range في تطبيقات إطار العمل Flask بعد أن تعرّفنا على الهدف من الدّالة range وكيفيّة استعمالها في مُختلف الحالات مع عدّة مُعاملات، لننتقل الآن إلى أمثلة واقعيّة لكيفيّة استخدام هذه الدّالة في تطوير تطبيقات الويب. المثال الأول، تكرار نصّ ما عدّة مرّات في تطبيقات الويب، قد ترغب بتكرار نصّ ما عدّة مرّات لتوضيح فكرة أو للحصول على صورة عامّة للصّفحة مع مُحتوى وهمي. يُمكنك استخدام الدّالة range() لتكرار نصّ ما أو مقطع HTML عددا من المرّات حسب العدد المُمرّر كمُعامل، فمثلا، يُمكننا تكرار المقطع “مرحبا بالعالم!” 10 مرّات باستعمال الشّيفرة التّالية: {% for number in range(10) %} <p>مرحبا بالعالم!</p> {% endfor %} لاحظ بأنّنا قد تعاملنا مع الدّالة range كقائمة عاديّة دون الحاجة إلى استعمال المُرشّح list على نحو صريح. المثال الثّاني، توليد أرقام الصّفحات لنقل بأنّنا نمتلك العديد من السّجلات في قاعدة بياناتنا ونرغب بعرضها على شكل صَفحات، بالإضافة إلى أنّنا نريد توفير طريقة بسيطة لنُمكّن المُستخدم من التّنقل عبر الصّفحات بِحُرّيَة. يُمكننا في هذه الحالة أن نُنشئ شريطا لأرقام الصّفحات ونربط كلّ رقم برابط الصّفحة التّابعة له. التّالي مثال بسيط على كيفيّة إنشاء شريط تصفّح يُغطّي أرقام الصّفحات من 1 إلى 9: {% for number in range(10) %} <span> {{ number }} </span> {% endfor %} النّتيجة: 0 1 2 3 4 5 6 7 8 9 في المثال لم نقم سوى بعرض كل رقم داخل وسم <span> وإن أردت تطوير هذا المثال أكثر، فسيلزمك استخدام الدّالة url_for التي يوفّرها إطار العمل Flask لربط كلّ رقم بالصّفحة الخاصّة به. الدّالة lipsum إن كانت لك خلفيّة في التّصميم فلا بدّ أن تكون قد سمعت عن نصّ lorem ipsum، إن كان الأمر كذلك، فالدّالة lipsum() ستُساعدك على توليد مقاطع عشوائيّة من هذا النّص لتستعملها في ملفّات HTML الخاصّة بك. إن لم تسمع من قبل عن lorem ipsum، فهو ببساطة نصّ لاتيني يُستعمل في التّصاميم الطّباعيّة كمثال على نصّ صحيح، وذلك لكي تتوضّح الصّورة لدى المُصمّم ومن يرى التّصميم، وقد أصبح يُستعمل كثيرا في تصاميم الويب لتأديّة نفس الواجب، بحيث يُستعمل على شكل فقرات وعناوين في ملفّات HTML لتوضيح التّصميم والحصول على فكرة عن الصّورة العامّة للتّصميم عندما تكون النّصوص مُتوفّرة، فمثلا إن كنت تُصمّم صفحة ويب لعرض مقالات ما، فستنقصك المقالات لتتوضّح صورة التّصميم بالنّسبة إليك، لذا فاللجوء إلى توليد هذا النّص العشوائي أسرع طريقة للحصول على تصميم يحتوي على مقالات وهميّة. طريقة استعمال الدّالة هي كما يلي: {{ lipsum() }} افتراضيّا، تولّد الدّالة lipsum خمسة فقرات، كلّ فقرة تحتوي على ما بين 20 إلى 100 كلمة، ويُمكنك تغيير هذه الإعدادات عبر تمرير مُعاملات إلى الدّالة. لتغيير عدد الفقرات المُولّدة، يُمكنك تمرير العدد الذي ترغبه كمُعامل. لذا فإن كنت ترغب بالحصول على فقرتين فقط، فستحصل على هذه النّتيجة كالتّالي: {{ lipsum(2) }} الحدّ الأدنى لعدد الكلمات في كلّ فقرة هو 20 كلمة، إن كنت ترغب بتعديل هذا فمرّر العدد الأدنى كقيمة للمُعامل min. التّالي مثال على كيفيّة جعل الدّالة lipsum تولّد فقرة واحدة تحتوي على 60 كلمة أو أكثر دون النّزول عن هذا الحدّ: lipsum(1, min=60) الحدّ الأقصى لعدد الكلمات المُولّدة يكون 100 كلمة بشكل افتراضي، لكن تغييره يُمكنك تمرير العدد الّذي تودّ أن يكون العدد الأقصى إلى المُعامل max. على سبيل المثال، الشّيفرة التّاليّة ستقوم بتوليد فقرتين عدد كلمات كلّ واحدة منهما بين 50 إلى 80 كلمة: {{ lipsum(2, min=50, max=80) }} مثال يجمع كلّا من الدّالة range والدّالة lipsum بعد أن تعرّفنا على كلّ من الدّالة range والدّالة lipsum، حان الوقت لاستعمالهما في مثال واحد لتتّضح لك فكرة استعمالهما وتحصل على أفكار أخرى لكيفيّة استغلال ما تعلّمته في هذا الدّرس. في هذا المثال، سنقوم بعرض ثمانيّة فقرات من نصّ lorem ipsum مع عنوان يعمل على شكل رابط بالإضافة إلى زرّ لتمكين الزّائر من قراءة المزيد. وسنستعمل إطار العمل Bootstrap للحصول على تصميم جميل. شيفرة المثال ستكون كما يلي: {% for i in range(8) %} <div class='col-md-3'> <h2> <a href='#'> {{ lipsum(1, min=3, max=5) }} </a> </h2> {{ lipsum(1, max=50) }} <button class='btn btn-default'>Read more...</button> </div> {% endfor %} نقوم أولا باستعمال حلقة for مع الدّالة range مع تمرير العدد 8 كمُعامل، لذا فأي شيفرة داخل حلقة for هذه ستتكرّر ثماني مرّات. داخل حلقة for، نستعمل وسم <div> وخاصيّة CSS col-md-3 التّي يُوفّرها لنا إطار العمل Bootstrap لتقسيم صفحة الويب لأجزاء مُتساويّة. داخل وسم <div> نستعمل الدّالة lipsum لتوليد نصّ عشوائي صغير ليظهر على شكل عنوان للمقال الوهمي، بعدها نستعمل الدّالة مُجدّدا لتوليد فقرة واحدة عدد كلماتها لا يتعدّى 50 كلمة، ثمّ بعد ذلك نعرض زرّا لقراءة المزيد. بعد تطبيق هذا المثال، سنحصل على نتيجة مُشابهة للصّورة التّاليّة: ختاماً تعرّفنا في هذا الدّرس على جزء آخر من أساسيّات مُحرّك القوالب Jinja، يُمكنك الآن استخدام كلّ من الدّالة range والدّالة lipsum لتطوير تطبيقات أفضل واختصار الكثير من الوقت عند العمل مع إطار العمل Flask، إضافة إلى أنّك ستستطيع استخدامها مع تطبيق إدارة المُحتوى “كلمة” والذي سنُكمل تطويره بعد إنهاء أساسيّات مُحرّك القوالب Jinja.
-
مُقدّمة بعد أن تعرّفنا على ماهية ميّزة الاختبارات في مُحرّك القوالب Jinja، وتعرّفنا على بعض من أهمّ الاختبارات المبنيّة مُسبقا في Jinja والتّي يُمكنك استعمالها مُباشرة لتطوير قوالبHTML الخاصّة بك، قد لا تجد ما يسدّ حاجتك في الاختبارات التّي درسناها، وكما فعلنا مع المُرشّحات، يُمكننا سدّ هذه الحاجة بإنشاء اختبارات خاصّة بنا لاختبار القيم والتّحقق ممّا إذا كانت تتّبع نمطا مُعيّنا أو لا. مبدأ اختبارات Jinja المُخصّصة في إطار العمل Flask قبل الانتقال إلى أمثلة تطبيقيّة حول كيفيّة إنشاء اختبارات خاصّة بك لاستعمالها في قوالب Jinja مع إطار العمل Flask، سنتعرّف أولا على الشّكل المبدئي للاختبار. كما استعملنا المُزخرف template_filter لإنشاء مُرشّحات خاصّة بنا، فإنّنا نستعمل مُزخرفا آخر لإنشاء اختبار مُخصّص، إذ نستعمل المُزخرف template_test مع كائن التّطبيق app لتمكين جميع القوالب من استخدام الاختبار. شيفرة اختبار بسيط هي كالآتي: @app.template_test(name='test_name') def test_name_function(value): # Test value return True الشّيفرة شبيهة بشيفرة إنشاء مُرشّح مُخصّص، الفرق أنّنا نستعمل المُزخِرف template_test ونُمرّر اسم الاختبار إلى المُعامل name، ثمّ نمنح للدّالة المُزَخْرَفَةِ اسما مُعبّرا، الدّالة تستقبل قيمة افتراضيّة (القيمة المُختبَرَة) تتواجد داخل المُعامل value، داخل الدّالة ستقوم بإجراءات لاختبار القيمة value مكان التّعليق Test value ثمّ تُرجع إمّا القيمة المنطقيّة True أو القيمة المنطقيّة False حسب ما إذا اجتازت القيمة الاختبار أو لا. طريقة استعمال الاختبار الذي تقوم بإنشائه هي نفسُها طريقة استعمال الاختبارات المبنيّة مُسبقا: variable is test_name لاحظ بأنّ الاختبار يجب أن يُرجع قيمة منطقيّة، ولتبسيط الأمور، فالمثال يُرجع دائما القيمة المنطقيّة True. مثال على اختبار للتّحقق من أنّ طول قيمة ما لا يتجاوز حدا مُعيّنا في هذا المثال سنقوم بإنشاء اختبار للتّحقق ممّا إذا كانت سلسلة نصيّة مُعيّنة أكبر من 30 محرفا، الأمر الذي سيضمن لنا تنسيقا أفضل للصّفحات، فمثلا، لنقل بأنّك ترغب بأن تكون عناوين المقالات في تطبيقك قصيرة، ولنقل بأنّك لا ترغب في عرض أي عنوان تزيد عدد أحرفه عن الثّلاثين حرفا لسبب أو لآخر. المثال التّالي يُوضّح كيفيّة إنشاء اختبار Jinja مُخصّص للتّحقّق من أنّ قيمة مُعيّنة أكبر من 30 محرفا: @app.template_test(name='long') def is_long_test(value): is_long = len(value) > 30 return is_long # True if value is more than 30 characters long نقوم أولا باستخدام المُزخرف app.template_test مع تمرير الاسم long إلى المعامل name وبالتالي سنتمكّن من استعمال الاختبار بالاسم long، في الدّالة المُزخرفة is_long_test نقوم بإنشاء مُتغيّر is_long يحمل إحدى القيمتين True أو False حسب ما إذا كان عدد أحرف السّلسلة النّصيّة value المُمرّرة افتراضيّا إلى الاختبار أكبر من 30 أو لا، للتّحقق ممّا إذا كانت كذلك، نستعمل المُعامل > (أكبر من) للمُقارنة بين طول القيمة value الذي نحصل عليه باستخدام الدّالة len والقيمة 30، في الأخير نُرجع قيمة المُتغيّر is_long. بعد إنشاء الاختبار في الملفّ project/__init__.py ستتمكّن من اختباره كما يلي: {{ 'Building A Web Application Using The Flask Framework' is long }} <br> {{ 'An Introduction To Flask' is long }} النّتيجة: True False في المثال اختبرنا قيمتين، الأولى عدد عناصرها أكبر من 30 محرفا، والقيمة الثّانية عبارة عن عنوان قصير لا يتعدّى الطّول الأقصى، لذا فمن الطّبيعي أن تكون نتيجة الاختبار الأول القيمة True لأنّ العنوان طويل بالفعل، ونتيجة اختبار القيمة الثّانية هي القيمة False لأنّ العنوان أقصر من الثّلاثين حرفا. يُمكنك كذلك ترك تحديد أكبر قيمة للمُطوّر عبر جعلها مُعاملا كما هو مُوضّح في المثال التّالي: @app.template_test(name='longer_than') def is_long_test(value, number_of_characters=30): is_long = len(value) > number_of_characters return is_long في هذا المثال أنشأنا اختبارا باسم longer_than، الدّالة المسؤولة عن الاختبار تقبل مُعاملا واحدا بالإضافة إلى القيمة value التّي تُمرّر افتراضيّا والتّي تُعتبر القيمة التّي تُختبر، بالنّسبة للمُعامل فقد سمّيناه number_of_characters لأنّه يُمثّل عدد المحارف الذي سنعتبره الحد الأقصى لسلسلة نصيّة، افتراضيّا قيمة المُعامل number_of_characters هي 30، ما يعني بأنّ الاختبار سيعمل بنفس طريقة عمل الاختبار long في المثال السّابق إذا لم تُوفّر قيمة للمُعامل. داخل الدّالة، الأمر مُشابه لطريقة عمل الاختبار long، لكن عوضا عن مُقارنة طول قيمة المُعامل value مع العدد 30، أصبحنا نُقارن الطّول مع قيمة المُعامل number_of_characters الذي نحصل عليه من مُستخدم الاختبار. هكذا سيكون لدينا اختبار يسمح لنا بالتّحقق ممّا إذا كانت سلسلة نصّيّة ما أطول من عدد مُعيّن أو لا، عوضا عن التّحقق ممّا إن كانت أطول من العدد 30 فقط. المثال التّالي توضيح لكيفيّة استخدام الاختبار longer_than الذي أنشأناه للتوّ: {% set title = 'An Introduction To Flask' %} {% if title is longer_than 40 %} 'Title is too long' {% else %} {{ title }} {% endif %} في هذا المثال، نُعرّف مُتغيّرا title ونمنحه سلسلة نصيّة قصيرة، بعدها نختبر هذا المُتغيّر باستعمال الجملة الشّرطيّة if والاختبار longer_than، نتأكّد في الاختبار من أنّ السّلسلة النّصيّة المتواجدة بالمُتغيّر title أطول من 40 محرفا، إن كانت كذلك فإنّنا نعرض الرّسالة 'Title is too long'، وإن لم تكن كذلك فإنّنا نعرضها على صفحة HTML بشكل طبيعي. بما أنّ العنوان غير طويل، فالنّتيجة ستكون عرض العنوان في الصّفحة وليس العكس: An Introduction To Flask لاختبار العكس استبدل سطر تعريف المُتغيّر title بما يلي: {% set title = 'Building A Web Application Using The Flask Framework' %} في هذه الحالة، العنوان أطول من 40 محرفا، لذا فالنّتيجة ستكون الجملة 'Title is too long'. مثال على اختبار للتّحقق ممّا إذا كانت سلسلة نصيّة ما تنتهي بمقطع مُعيّن في لغة بايثون، يُمكننا التّحقق من أنّ سلسلة نصيّة تنتهي بمقطع ما باستعمال التّابع endswith مع تمرير المقطع كمُعامل. مثال على كيفيّة استخدام التّابع endswith داخل مُفسّر لغة بايثون: >>> 'An Introduction To Flask'.endswith('Flask') True >>> 'An Introduction To Flask'.endswith('Python') False المُهمّة هي إنشاء اختبار Jinja للحصول على نفس النّتيجة، بحيث سيكون لدينا اختبار باسم endswith ويقبل مُعاملا واحدا، ألا وهو المقطع الذي سنتحقّق ممّا إذا كان مُتواجدا بنهاية السّلسلة المُختَبَرَةِ أو لا. ما يلي مثال على كيفيّة إنشاء الاختبار endswith: @app.template_test(name='endswith') def endswith_test(value, end): return value.endswith(end) الاختبار بسيط جدّا، إذ كل ما نقوم به هو استعمال التّابع endswith على القيمة value مع تمرير قيمة المُعامل end التّي تُعتبر المقطع الذي نرغب بالتّحقق من أنّ السّلسلة تنتهي به، وبما أنّ نتيجة استعمال التّابع ستكون إمّا القيمة True أو القيمة False، فالاختبار سيعمل دون مشاكل، لأنّ شرط اختبار أن تُرجع إحدى القيمتين. المثال التّالي توضيح لكيفيّة استعمال الاختبار endswith: {% set title = 'Building A Web Application Using Flask' %} {% if title is endswith('Flask') %} 'Title ends with the word Flask' {% else %} 'Title does not end with the word Flask' {% endif %} في المثال، نقوم بالتّعريف الاعتيادي للمُتغيّر title، مع تعيين عنوان ينتهي بالكلمة Flask، بعدها نستعمل الاختبار للتّحقق ممّا إذا كان المُتغيّر title ينتهي بالمقطع Flask أو لا، فإن كان كذلك فإنّنا نعرض الرّسالة 'Title ends with the word Flask' وإن لم يكن كذلك فالرّسالة تُصبح 'Title does not end with the word Flask'. وبما أنّ العنوان ينتهي بالمقطع Flask فالنّتيجة هي الحالة الأولى: 'Title ends with the word Flask' لاختبار الحالة الثّانية، غيّر سطر تعريف المُتغيّر title إلى جملة لا تنتهي بالمقطع Flask. خاتمة بنهاية هذا الدّرس، يجب أن تكون قادرا على كتابة واستخدام اختباراتك الخاصّة عند العمل مع كل من مُحرّك القوالب Jinja وإطار العمل Flask، في الدّروس القادمة، سنُكمل التّعرف على أساسيات مُحرّك القوالب لتتمكّن من تطوير تطبيقات Flask أفضل.
-
مُقدّمة بعد التّعرف في الدّرس السّابق على مفهوم الاختبارات (Tests) في مُحرّك القوالب Jinja، سنتعرّف في هذا الدّرس على بعض من أهمّ الاختبارات التّي يجب عليك الإلمام بها لتطوير تطبيقات ويب أفضل بشكل أسهل وأسرع مع إطار العمل Flask، بعد التّعرف على هذه الاختبارات، يُمكنك استعمالها ببساطة للتّحقق من أنّ القيمة المتواجدة بمُتغيّر ما هي ما تُريده بالفعل، كما ستتمكّن من تنفيذ إجراءات حسب حالة المُتغيّر. الاختبار iterable قُلنا من قبل بأنّ الكائنات من النّوع iterable عبارة عن أي كائن يُمكنك الدّوران حوله باستخدام حلقة for، هذا يشمل مجموعات العناصر مثل القوائم والسّلاسل النّصيّة وأنواع أخرى من القيم، إذا لم تكن متأكّدا من أنّ كائنا ما عبارة عن كائن من النّوع Iterable فيُمكنك استخدام الاختبار iterable للتّحقق من الأمر بشكل بسيط. مثلا، يُمكن أن تجد بأنّك ستحتاج إلى الدّوران حول مُتغيّر لكنّك لست متأكّدا من أنّه سيكون دائما قابلا لذلك، لذا لتفادي عرض الأخطاء للمُستخدم، يُمكنك استخدام الاختبار iterable مع جملة if شرطيّة، وهكذا ستتمكّن من الدّوران حول المُتغيّر فقط إذا كان من النّوع Iterable أمّا إن لم يكن كذلك فسيتم تجاهله إلى أن تتغيّر قيمته إلى نوع قابل للدّوران عليه. المثال التّالي توضيح لكيفيّة استعمال الاختبار Iterable: {% set variable = 'ABCD' %} {% if variable is iterable %} <ul> {% for item in variable %} <li>{{ item }}</li> {% endfor %} </ul> {% else %} لا يُمكنك الدّوران حول قيمة المُتغيّر {% endif %} المثال بسيط وواضح، إذ نقوم بتعريف المُتغيّر variable ونتحقّق من أنّه من النّوع Iterable عن طريق كل من الجملة الشّرطيّة if والاختبار iterable، إذا كانت قيمة المُتغيّر قابلة للدّوران عليها، فإنّنا نقوم باستعمال الحلقة for للوصول إلى كل عنصر وعرضه على شكل عنصر من قائمة HTML، أمّا إن لم تجتز قيمة المُتغيّر الاختبار فإنّنا نعرض الجملة “لا يُمكنك الدّوران حول قيمة المُتغيّر”. بما أنّنا نستطيع الدّوران حول أي سلسلة نصيّة في لغة بايثون للوصول إلى عناصرها، فنتيجة المثال ستكون قائمة HTML لعرض عناصر السلسلة النّصيّة ABCD. لنتأكّد من أنّ المثال يعمل في كلتا الحالتين، غيّر السّطر الأول إلى ما يلي: {% set variable = 100 %} في هذه الحالة، قيمة المُتغيّر هي العدد الصّحيح 100، والأعداد الصّحيحة نوع لا يُمكن أن تدور حوله (إلّا إذا ما حوّلته إلى سلسلة نصيّة)، لذا فالنّتيجة هذه المرّة ستكون الجملة “لا يُمكنك الدّوران حول قيمة المُتغيّر ”. الاختبار lower عند التّعامل مع الأحرف اللاتينيّة، قد ترغب بالتّحقق من أنّ قيمة مُتغيّر عبارة عن أحرف صغيرة (Lowercase)، وهذا بالضّبط ما يُمكنك فعله بالاختبار lower. المثال التّالي يُوضّح طريقة عمل الاختبار lower: {% set words = ['ORM', 'walk', 'run', 'OOP'] %} {% for word in words %} {% if word is lower %} {{ word }} {% else %} {{ word }} is an acronym. {% endif %} <br> {% endfor %} في المثال أعلاه، نُعرّف قائمة باسم words تحتوي على بضعة كلمات، بعضها عبارة عن كلمات عاديّة والبعض الآخر عبارة عن اختصارات، وكما تعلم فهذه الأخيرة تُكتب بأحرف كبيرة لذا سنتمكّن من فصلها عن الكلمات الأخرى باستخدام الاختبار lower لأنّ الكلمات العاديّة مكتوبة بأحرف صغيرة، ما يعني بأنّها ستجتاز الاختبار، أمّا الاختصارات فلن تجتازه، لذا نقوم بالدّوران حول القائمة واختبار كل عنصر، فإن كانت الكلمة تحتوي على الأحرف الصّغيرة فقط فإنّنا نعرضها أمّا إن لم تكن كذلك فإنّنا نعرض رسالة تُفيد بأنّ العنصر عبارة عن اختصار لعبارة ما. نتيجة المثال أعلاه ستكون كما يلي: ORM is an acronym. walk run OOP is an acronym. الاختبار upper هذا الاختبار هو عكس الاختبار lower، إذ يُستعمل للتّحقق من أنّ كلمة ما مكتوبة بأحرف كبيرة، وطريقة استخدامه مُشابهة لطريقة استخدام الاختبار lower. مثال على كيفيّة استخدام الاختبار upper: {% set words = ['ORM', 'walk', 'run', 'OOP'] %} {% for word in words %} {% if word is upper %} {{ word }} <br> {% endif %} {% endfor %} النّتيجة: ORM OOP الاختبار undefined تعرّفنا من قبل على الاختبار defined، وقلنا بأنّه يُستعمل للتّحقق من أنّ مُتغيّرا ما معرّف أو لا، الاختبار undefined يقوم بالعكس، إذ يرجع القيمة المنطقيّة True إذا لم يكن المُتغيّر مُعرّفا، والقيمة False إذا كان المُتغيّر مُعرّفا. الاختبار none في لغة بايثون، إذا كان مُتغيّر ما يحمل القيمة None فهذا يعني بأنّ المُتغيّر مُعرّف لكنّه فارغ ولا يحمل أية قيمة، هذه القيمة مُهمّة جدّا إذا كنت تتعامل مع بعض المكتبات التّي تُساعد على التّعامل مع قواعد البيانات مثل مكتبة SQLAlchemy التّي سنعتمد عليها بشكل أساسيّ في مشروع “كلمة” الذي نبنيه في هذه الدّروس، لذا فمعرفة الغرض من القيمة None مهم. الاختبار none الذي يُمكنك استعماله في قوالب Jinja سيُساعدك على التّحقق ممّا إذا كانت قيمة الكائن الذي تتعامل معه فارغة أو لا، وبالتّالي ستتمكّن من عرض رسالة مفهومة للمُستخدم في حالة كانت نتيجة ما يبحث عنه هي القيمة None، فمثلا يُمكنك عرض الرّسالة “هذا المُستخدم لم يُضف نبذة عن نفسه بعد” في الصفحة الشّخصيّة للمُستخدمين الذين لم يقوموا بإضافة نبذة شخصيّة إلى حساباتهم، وهكذا سيعرف الزائر سبب غياب أي وصف في المكان المخصّص لذلك. المثال التّالي يُوضّح استعمال الاختبار none مع جملة شرطيّة لعرض رسالة تنبيه في حالة كانت قيمة العنصر فارغة: {% set list = [None, 'One', None, 'Two'] %} {% for item in list %} {% if item is none %} قيمة العنصر فارغة <br> {% else %} {{ item }} <br> {% endif %} {% endfor %} في المثال أعلاه، مزجنا بين السّلاسل النّصيّة والقيمة None في عناصر القائمة list، بعدها نقوم بالدّوران حول القائمة بحلقة for، ثمّ بعد ذلك نتحقّق ممّا إذا العنصر الحالي عبارة عن قيمة فارغة، فإن كان الأمر كذلك فإنّنا نعرض الرّسالة “قيمة العنصر فارغة”، أمّا إن لم يكن العنصر فارغا فإنّنا نعرضه. النّتيجة: قيمة العنصر فارغة One قيمة العنصر فارغة Two الاختبار number لمعرفة ما إذا كانت قيمة كائن ما عبارة عن عدد أو لا، يُمكنك استخدام الاختبار number، مع التّأكيد على أنّ الأعداد العشريّة تُعتبر أعدادا كذلك، لذا ستجتاز الاختبار بشكل عادي. المثال التّالي توضيح على كيفيّة استخدام الاختبار number: {% set list = [1, 'Flask', 2, 3, 3.2, 0.001] %} {% for item in list %} {% if item is number %} {{ item }} عبارة عن عدد <br> {% else %} {{ item }} ليس بعدد <br> {% endif %} {% endfor %} في هذا المثال، نقوم بتعريف قائمة تحتوي على أعداد وسلاسل نصيّة، ندور حول القائمة باستعمال حلقة for ثمّ نستعمل الاختبار number للتّحقّق ممّا إذا كان العنصر عددا أو لا، فإن كان عددا فإنّنا نعرض رسالة تُفيد بأنّ العنصر عبارة عن عدد بالفعل، أمّا إن لم يكن العنصر كذلك فإنّنا نعرض رسالة مفادها بأنّ القيمة ليست بعدد. نتيجة المثال: 1 عبارة عن عدد Flask ليس بعدد 2 عبارة عن عدد 3 عبارة عن عدد 3.2 عبارة عن عدد 0.001 عبارة عن عدد لاحظ في النّتيجة بأنّ الأعداد العشريّة تجتاز الاختبار كذلك. الاختبار string للتّحقق من أنّ قيمة ما عبارة عن سلسلة نصيّة، يُمكنك استخدام الاختبار string الذي يعمل بطريقة مُشابهة لطريقة عمل الاختبار number، ويُمكنك استعماله عند التّعامل مع البيانات التّي يُمكن أن تكون سلاسل نصيّة كما يُمكن أن تكون قيما أخرى في قاعدة البيانات. مثال على كيفيّة استخدام الاختبار string: {% set list = ['Flask', 2017, None] %} {% for item in list %} {% if item is number %} {{ item }} عبارة عن عدد <br> {% elif item is string %} {{ item }} عبارة عن سلسلة نصيّة <br> {% else %} {{ item }} ليس بعدد وليس سلسلة نصيّة <br> {% endif %} {% endfor %} في المثال أعلاه، نقوم بإنشاء قائمة ثمّ ندور عليها كالعادة، نختبر ما إذا كان كل عنصر عبارة عن عدد بالاختبار number لعرض رسالة تُفيد بذلك، إذا لم تكن القيمة عددا فإنّنا نستعمل الاختبار string لنرى إذا ما كانت القيمة سلسلة نصيّة لنعرض رسالة مفادها أنّ القيمة سلسلة نصيّة بالفعل، إذا لم تكن القيمة لا عددا ولا سلسلة نصيّة فإنّنا نعرض الجملة “ليس بعدد وليس سلسلة نصيّة ”. النّتيجة: Flask عبارة عن سلسلة نصيّة 2017 عبارة عن عدد None ليس بعدد وليس سلسلة نصيّة ختاما هكذا نكون قد تعرّفنا على ماهيّة الاختبارات في مُحرّك القوالب Jinja، وألقينا نظرة إلى بعض من أهمّ الاختبارات المبنيّة مُسبقا وعلى كيفيّة استخدامها في تطبيقات Flask أو أي مشروع بايثون آخر يعتمد على Jinja، سننتقل في الدّرس القادم إلى كيفيّة إنشاء اختبارات خاصّة بنا لاستعمالها في حالة لم تجد اختبارا يُلبي حاجتك في قائمة الاختبارات المبنيّة مُسبقا.
-
مُقدّمة بعد أن تعرّفنا على ماهيّة المُرشّحات وكيفيّة استخدام أهم المُرشّحات المبنيّة مُسبقا في Jinja وبعد أن تعرّفنا على كيفيّة إنشاء مُرشّحات خاصّة بنا، سننتقل إلى ميّزة أخرى لا تقلّ أهميّة عن المُرشّحات، ألا وهي ميزة الاختبارات Tests في مُحرّك القوالب Jinja، إذ سنتعرّف أولا على ماهيّة الاختبارات والغرض من استعمالها وكيفيّة الاستفادة منها في تطبيقات Flask الخاصّة بك ثمّ سننتقل إلى التّعرف على بعض من أهم الاختبارات التّي يُمكنك الاعتماد عليها لتطوير تطبيقاتك. ما معنى اختبارات Jinja في ما سبق من الدّروس، تعرّفنا على حقيقة أنّ مُرشّحات Jinja تقوم بتحويل قيمة من حالة إلى أخرى، في اختبارات Jinja لا تتحوّل القيمة، بل تُختبر ضدّ اختبار (Test) للتأكد من أنّها تندرج ضمن حالة مُعيّنة، ومنه تستطيع تطبيق اختبار على كائن للتّحكم بما يُعرض في المُتصفّح وما لا يُعرض حسب ما إذا اجتاز اختبارا أو لم يجتزه. طريقة إجراء الاختبارات هي باستعمال الكلمة المفتاحيّة is عوضا عن الرّمز | الذي يُستعمل لتطبيق مُرشّح مُعيّن. على سبيل المثال، يُمكنك التّحقق ممّا إذا كان مُتغيّر مُعرّفا أو لا كما يلي: name is defined في المثال أعلاه اسم المُتغيّر الذي يُجرى عليه الاختبار هو name والاختبار هو defined، وهو اختبار مُتوفّر مُسبقا في مُحرّك القوالب Jinja، وكما فعلنا مع المُرشّحات المبنيّة مُسبقا، فسنتعرّف على أهم الاختبارات التّي يُمكنك الاستفادة منها مُباشرة مع Jinja. يُمكن كذلك للاختبارات أن تقبل مُعاملات لمرونة أكثر في الاستخدام، فمثلا يُمكنك استخدام الاختبار divisibleby للتّحقق من أنّ عددا مُعيّنا يقبل القسمة على عدد آخر، العدد الأول هو القيمة التّي تُجرى عليها الاختبار، أمّا العدد الثّاني فهو القيمة المُمرّرة إلى الاختبار كمُعامل. مثال: 9 is divisibleby(3) إذا كان الاختبار يقبل مُعاملا واحدا فقط، فيُمكنك تمرير المُعامل دون أقواس، لذا فالمثال التّالي يقوم بنفس الشّيء الذي يقوم به المثال أعلاه: 9 is divisibleby 3 نتيجة الاختبار ستكون إمّا القيمة المنطقيّة True أو القيمة المنطقيّة False، ما يعني بأنّك تستطيع الاستفادة من نتيجة الاختبار باستعمالها مع الجمل الشّرطية if و else و elif. لتفهم هذا الدّرس أكثر، يُمكنك استعمال ملفّ filters.html الذي أنشأناه سابقا لتطبيق الأمثلة وتجربة أفكارك إذا ما شئت، إذ يُمكنك تفعيل البيئة الوهميّة وتعريف مُتغيّرات البيئة ومن ثمّ تشغيل الخادوم المحلي لتطبيق “كلمة” عبر كتابة الأمر flask run في سطر الأوامر، وبعدها كتابة المثال التّالي في ملفّ filters.html وزيارة العنوان http://127.0.0.1:5000/filters لتُلاحظ نتيجة تطبيق الاختبار divisibleby مع أعداد مُختلفة. المثال: {{ 10 is divisibleby 2 }} <br> {{ 4 is divisibleby 3 }} <br> {{ 9 is divisibleby 2 }} <br> {{ 60 is divisibleby 6 }} النّتيجة: True False False True يُمكنك تغيير الأعداد كما تشاء، والنّتيجة ستختلف حسب ما إذا كان العدد الأول قابلا للقسمة على العدد الآخر أم لا. ما الغرض من استعمال الاختبارات؟ قد لا تجد فائدة كثيرة في الاختبارات بعد أن تعرّفت على ماهيّتها، لكن الأمر أبعد من اختبار ما إذا كانت القيمة تقبل القسمة على عدد أم لا، إذ تستطيع استعمال الاختبارات لتحديد ما إذا كان كائن قابلا للاستدعاء أم لا، أو ما إذا كان يُساوي قيمة مُعيّنة أو التّأكّد من أنّ شيفرة مُعيّنة قد مُنعت من العرض على المُتصفّح كما هي لتفادي الهجمات الخبيثة، بالإضافة إلى الكثير من الأشياء الأخرى التّي يُمكنك أن تستعمل الاختبارات لأجلها، ما سيُتيح لك تحكّما أكثر بما يُعرض على مُتصفّح مُستخدم تطبيقك وما لا يُعرض. وبالإضافة إلى ما سبق، فستتعرّف على أهميّة الاختبارات حالما نُلقي نظرة على بعض من أهم الاختبارات التّي يُوفرّها مُحرّك القوالب Jinja وكيفيّة الاستفادة منها، ما سيُساعدك على معرفة الغرض من هذه الخاصيّة وأهمّيتها. قائمة ببعض من أهم اختبارات مُحرّك القوالب Jinja الآن وقد تعرّفنا على ماهيّة الاختبارات في مُحرّك القوالب Jinja، حان الوقت للتّعرف على أهمّ الاختبارات المبنيّة مُسبقا في Jinja والتّي يُمكنك استخدامها مُباشرة. الاختبار defined بالإضافة للمُرشّح default الذي تعرّفنا عليه مُسبقا، والذي يُمكّننا من توفير قيمة افتراضيّة لمُتغيّر مُعيّن في حالة لم يكن مُعرّفا، فالاختبار defined يُمكّنك من التّعرف على ما إذا كان مُتغيّر مُعيّن مُعرّفا أو لا، ما سيُمكّنك من اتّخاذ الإجراءات اللازمة في حالة لم يكن مُعرّفا. يُمكنك استخدام المثال التّالي لتفهم أكثر الغرض من الاختبار defined. {% if variable is defined %} قيمة المُتغيّر: {{ variable }} {% else %} المُتغيّر غير مُعرّف {% endif %} المثال أعلاه يتحقّق ممّا إذا كان المُتغيّر variable مُعرّفا أو لا، إذا كان مُعرّفا فالنّتيجة ستكون الجملة “قيمة المُتغيّر: ” بالإضافة إلى قيمة المُتغيّر، أمّا إذا لم يكن المُتغيّر مُعرّفا فالنّتيجة ستكون الجملة “المُتغيّر غير مُعرّف”. إذا قُمت بتجربة المثال في ملفّ filters.html فستحصل على النّتيجة الثّانيّة، ولتغيير الوضع يُمكنك تعريف المُتغيّر فوق جملة if الشّرطية كما يلي: {% set variable = 'مرحبا بالعالم!' %} بعد تعريف المُتغيّر ستجد بأنّ النّتيجة أصبحت الجملة “قيمة المُتغيّر: مرحبا بالعالم! ” عوضا عمّا سبق. الاختبار callable باستعمال الاختبار callable يُمكنك التّحقق ممّا إذا كان كائن أو مُتغيّر ما قابلا للاستدعاء أو لا، الكائنات القابلة للاستدعاء هي الدّوال، الماكرو وأصناف لغة بايثون وتوابع الأصناف وبعض الأنواع الأخرى. المثال التّالي سيُساعدك على فهم الأمر: {% macro say_hello() %} مرحبا بالعالم! {% endmacro %} {% if say_hello is callable %} الكائن قابل للاستدعاء ونتيجة استدعائه هي: <br> {{ say_hello() }} {% else %} لا يُمكن استدعاء الكائن {% endif %} في المثال، أنشأنا ماكرو بسيطا يقوم بإرجاع الجملة “مرحبا بالعالم!” بعدها طبّقنا الاختبار callable على الماكرو، والنّتيجة هي كما يلي: الكائن قابل للاستدعاء ونتيجة استدعائه هي: مرحبا بالعالم! ما يعني بأنّ الماكرو قابل للاستدعاء، ولو لم يكن كذلك لكانت النّتيجة هي الجملة “لا يُمكن استدعاء الكائن”. يُمكنك اختبار الحالة الأخرى (عدم قابليّة استدعاء الكائن) عبر حذف الماكرو وتعريف المُتغيّر say_hello مكان الماكرو باستعمال السّطر التّالي: {% set say_hello = 'مرحبا بالعالم!' %} وبما أنّ مُتغيّرا عاديّا لا يُمكن استدعاؤه، فالنّتيجة ستكون الجملة “لا يُمكن استدعاء الكائن ”، وذلك لأنّ الاختبار callable يُرجع القيمة المنطقيّة Flase عند استعماله مع متغيّرات بايثون عاديّة أو خصائص الأصناف لأنّها غير قابلة للاستدعاء. الاختباران even و odd إذا أردت التّحقق ممّا إذا كان عدد ما زوجيا أو لا فيُمكنك استعمال الاختبار even للقيام بالأمر، إذ يُرجع الاختبار إذا ما طُبق على عدد القيمة المنطقيّة True إذا كان العدد زوجيّا، أمّا إن لم يكن كذلك فالنّتيجة ستكون القيمة False. أمّا الاختبار odd فيقوم بالعكس، إذ يُستعمل للتّحقق من أنّ عددا ما فردي أو لا. المثال التّالي توضيح لكيفيّة استخدام كل من even و odd: {% set numbers = [1, 2, 3, 4, 5, 6] %} {% for number in numbers %} {% if number is even %} العدد {{ number }} عدد زوجي <br> {% elif number is odd %} العدد {{ number }} عدد فردي <br> {% endif %} {% endfor %} في المثال أعلاه، نُعرّف أولا قائمة أعداد ثمّ ندور حولها باستخدام حلقة for، في كل دورة نتحقّق ممّا إذا كان العدد زوجيّا لعرض رسالة تُفيد بأنّ العدد زوجي، وإن لم يكن العدد زوجيّا نتحقّق مّما إذا كان عددا فرديا لنعرض رسالة مضمونها أنّ العدد فردي. النّتيجة: العدد 1 عدد فردي العدد 2 عدد زوجي العدد 3 عدد فردي العدد 4 عدد زوجي العدد 5 عدد فردي العدد 6 عدد زوجي ختاما تعرّفنا في هذا الدّرس على ماهيّة ميّزة الاختبارات في مُحرّك القوالب Jinja، وتعرّفنا على الغرض منها وكيفيّة استعمالها وعلى بعض الاختبارات المبنيّة مُسبقا، في الدّرس القادم، سنُكمل ما بدأناه بالتّعرف على المزيد من الاختبارات المتواجدة بمُحرّك القوالب Jinja والتّي ستُساعدك على تطوير تطبيقات ويب أفضل كما ستُفيدك في فهم وتطوير تطبيق “كلمة”.
-
مُقدّمة تعرّفنا في الدّروس السّابقة على كيفيّة الاستفادة من مُحرّك القوالب Jinja أثناء تطوير تطبيقات ويب باستخدام إطار العمل Flask ، خاصّة ميّزتي الماكرو والمُرشّحات، اللّتان تُعتبران من أهمّ ميّزات Jinja، وبعد أن ألقينا نظرة على أهم المُرشّحات المبنيّة مُسبقا، يُمكن ألّا تجد ما يسدّ حاجتك في هذه المُرشّحات وقرّرت أنّك تملك أفكارا أخرى يُمكن أن تحوّلها إلى مُرشّح لمُساعدتك على تفادي تكرار الشّيفرة والتطوير بشكل أسرع، إن كان الأمر كذلك، فهذا الدّرس هو كل ما تحتاج إليه لتعلّم كيفيّة إنشاء مُرشّحات أخرى وكيفيّة استعمالها مع إطار العمل Flask، إذ سنستخدم مشروع “كلمة” الذي نحن بصدد بنائه كمثال على كيفيّة إنشاء واستخدام مُرشّحاتك الخاصّة. إنشاء مُرشّحات خاصّة بك بعد أن تعرّفنا على كيفيّة استعمال المُرشّحات المبنيّة مُسبقا في Jinja، سننتقل إلى جزء آخر مهمّ لتطوير تطبيقات الويب باستعمال إطار العمل Flask ومُحرّك القوالب Jinja، وهو كيفيّة إنشاء مُرشّحات خاصّة بك. لكن قبل أن ننتقل إلى كيفيّة إنشاء مُرشّح جديد، يجب أن تُدرك بأنّ هناك أكثر من طريقة لإنشاء مُرشّح مع إطار العمل Flask ولأنّ طريقة إنشاء مُرشّح يُشبه إلى حد ما طريقة إنشاء مُوجّه ما، فمكان إنشاء المُرشّح مُهمّ، إذ يُحدّد المكان الذي تُنشئ فيه المُرشّح القوالبَ التّي تستطيع الوصول إلى هذا المُرشّح، وهذا لأنّ سياق التّطبيق يكون مُختلفا بين المُخطّطات blueprints والكائن app الرّئيسيّ إذ أنّ هذا الأخير يُعتبر عامًا (أي أنّ جميع القوالب تستطيع الوصول إلى المُرشّح) وعند استخدام مُخطّط لإنشاء مُرشّح، فالقوالب التّي ستتمكّن من الوصول إلى المُرشّح هي فقط القوالب المُرتبطة بالمُخطّطات ولن تتمكّن من استخدام المُرشّح في القوالب الأخرى. بمعنى آخر، إن أنشأت مُرشّحا في المُخطّط الخاصّة بالمُستخدمين فلن تستطيع استخدامه سوى على قوالب HTML المتواجدة بمُجلّد templates في المسار project/users. والمُرشّحات التّي تُنشأ في المخطّط الخاصّ بالمقالات لن تستطيع استخدامها سوى مع القوالب الخاصّة بالمقالات، أمّا إن أردت إنشاء مُرشّحات للوصول إليها بشكل عام في كل التّطبيق بحيث تتمكّن جميع قوالب HTML من الوصول إلى المُرشّح واستخدامه فسيتوجّب عليك إنشاؤه باستعمال الكائن app الذي يتواجد في ملفّ __init__.py داخل مُجلّد المشروع project أي المسار project/__init__.py . بما أنّنا نستعمل الملفّ filters.html كحقل تجارب، وبما أنّه لا ينتمي إلى مُخطّط مُعيّن، فسنقوم بإنشاء المُرشّحات في هذه الفقرة باستخدام الكائن app، لكن إن رأيت في تطبيقاتك بأنّ مُرشّحا ما خاص بمُخطّط مُعيّن ولا حاجة لمُشاركته مع قوالب HTML الأخرى، فلك الحريّة في إنشائه تحت مُخطط وليس باستعمال الكائن app. كيف يعمل المُرشّح فكرة المُرشّح بسيطة، إذ يأخذ قيمة ما كمُعامل ويُرجع قيمة أخرى، والتّالي نموذج يُبيّن طريقة إنشاء مُرشّح خاصّ بك: @app.template_filter('filter_name') def filter_name_function(value): return value المُرشّح أعلاه شبيه جدّا بمُوجّه عادي، بحيث نستخدم مُزخرفا template_filter ونُعطيه اسما ما بين القوسين، والدّالة المُزَخرَفةُ تأخذ قيمة value ومن ثمّ تُرجع نفس القيمة أمّا اسم الدّالة فهو غير مُهمّ، ما يعني بأنّك تستطيع تسميّة الدّالة بأي اسم تُريده إلّا أنّه من المُفضّل أن يصف تأثير المُرشّح، فمثلا، مُرشّح لعرض الاسم الأول لمُستخدم يجب أن تُسمّى دالّته بالاسم first_name_filter الشّيء الذي يصف الدّالة والمُرشّح والهدف بشكل واضح ومفهوم لمن يقرأ شيفرة التّطبيق. لاستخدام المُرشّح سيتوجّب عليك استخدام اسمه الذي خصّصته ما بين قوسين (أي الاسم filter_name في المثال أعلاه)، كما يلي: {{ value | filter_name() }} إن جرّبت المثال فستُلاحظ بأنّ القيمة value لا تتغيّر، وهذا لأنّ ما يُرجعه المُرشّح هو نفسه القيمة التّي يأخذها. لتفهم أهميّة السّطر return، جرّب تغييره إلى ما يلي: @app.template_filter('filter_name') def filter_name_function(value): return 'Hello World!' الآن، المُرشّح لا يقوم سوى بمُهمّة واحدة، بحيث يقوم بتحويل أيّ قيمة يستقبلها إلى القيمة Hello World! ما يعني أنّك إذا استخدمته في أي مكان في قالب HTML فستتحوّل القيمة إلى Hello World!. سنتطرّق لاحقا إلى أمثلة أكثر واقعيّة، لكن عليك أن تفهم بأنّ القيمة التّي تُرجعها الدّالّة المسؤولة عن المُرشّح هي القيمة النّهائيّة التّي ستظهر على المُتصفّح لذا فلتحويل القيمة التي تستقبلها سيتوجّب عليك القيام بكل شيء داخل الدّالة قبل إرجاع أي شيء بالجملة return. أمثلة على إنشاء مُرشّحات وكيفيّة استعمالها بالمثال يتّضح المقال، ولأنّ مُرشّحات Jinja تُستخدم بطرق مُختلفة وكثيرة، مثال واحد لا يكفي، إذ يُمكن للمُرشّح أن يكون بسيطا كما يُمكن أن يكون مُعقّدا، لذا ففي الأمثلة التّاليّة سننتقل من أمثلة بسيطة إلى أمثلة أكثر تعقيدا، وستُساعدك هذه الأمثلة على فهم كيفيّة إنشاء مُرشّح، طريقة استخدامه والأهم من ذلك، ستكتسب فكرة عن الحالات التّي سيتوجّب عليك التّوقف فيها عن تطوير تطبيقك وكتابة مُرشّح لمُساعدتك على تنفيذ مُهمّة بشكل أسرع، بل تستطيع حتى أن تحفظ المُرشّحات التّي تكتبها لاستعمالها في أكثر من تطبيق، فمثلا إن أنشأت مُرشّحا لترجمة كلمات شائعة في تطبيقك (المثال الأول)، فقد تستفيد منه في تطوير تطبيق آخر يتطلّب إنجاز المُهمّة ذاتها، وإن نشرته على منصّة مثل منصّة Github فقد يستفيد منه أشخاص أكثر. كيفيّة إنشاء مُرشّح Jinja خاص بك، المثال الأول: مُرشّح ترجمة الكلمات في هذا المثال الأول، سنقوم بإنشاء مُرشّح لترجمة كلمات من اللغة الانجليزيّة إلى اللغة العربيّة، وسيعتمد هذا المثال على قاموس Python بسيط يحتوي على بضعة كلمات ومُقابلاتها في اللغة العربيّة. سيكون المُرشّح كالتّالي: @app.template_filter('translate') def translate_filter(value): english_to_arabic = {'Hello': u'مرحبا', 'Flask': u'فلاسك', 'Python': u'بايثون', 'Javascript': u'جافاسكربت', 'Academy': u'أكاديميّة' } arabic_word = english_to_arabic[value] return arabic_word ستُلاحظ أولا بأنّنا سمّينا المُرشّح بالاسم translate وهذا هو الاسم الذي سنستعمله إذا ما أردنا تطبيق المُرشّح في قوالب HTML، أمّا الدّالة فاسمها translate_filter وكما قلت سابقا، فاسم الدّالة لا يهم، لكن من المُفضّل أن يشرح الاسم هدف الدّالة. بعدها أنشأنا القاموس english_to_arabic ليحتوي على بضعة كلمات إنجليزيّة ونظيراتها باللغة العربيّة كمثال بسيط، وبالطّبع، ففي مثال واقعي، سيتوجّب عليك الحصول على الكلمات وترجماتها من قاعدة بيانات وليس من قاموس عادي. في السّطر ما قبل الأخير، نُعرّف المُتغيّر arabic_word لتحمل الكلمة العربيّة التي نحصل عليها من القاموس باستعمال القيمة value التّي تُمرّر إلى المُرشّح كمفتاح. أمّا في السّطر الأخير فإنّنا نقوم بإرجاع مُحتوى المُتغيّر arabic_word. المثال التّالي يوضّح كيفيّة استعمال المُرشّح الذي أنشأناه للتّو: {{ 'Python' | translate }} النّتيجة ستكون الكلمة "بايثون” لأنّها الكلمة التي تُعتبر قيمة للمفتاح Python في القاموس english_to_arabic. المثال التّالي يشمل جميع الكلمات المُتواجدة في القاموس الصّغير الذي أنشأناه: {% set words = ['Flask', 'Python', 'Hello', 'Javascript', 'Academy'] %} {% for word in words %} <p> {{ word }}: {{ word | translate }} </p> {% endfor %} النّتيجة: Flask: فلاسك Python: بايثون Hello: مرحبا Javascript: جافاسكربت Academy: أكاديميّة كيفيّة إنشاء مُرشّح Jinja خاص بك، المثال الثّاني في هذا المثال، سنقوم بإنشاء مُرشّح للحصول على إجمالي الأحرف في قائمة ما، ما يعني بأنّنا سنجمع العناصر النّصيّة في القائمة لتكون على شكل سلسلة نصيّة واحدة، بعدها سنحسب عدد الأحرف في السّلسلة النّصيّة النّاتجة، ومنه نُرجع النّاتج ليُعرض في المُتصفّح. إليك شيفرة المُرشّح: @app.template_filter('list_len') def list_length_filter(value): joined_list = "".join(value) length = len(joined_list) return length إن تمعّنت في الشّيفرة فستفهمها لأنّ الفكرة وراءها سهلة، أولا، نقوم بضم جميع القيم في مجموعة العناصر value ونُخزّن النّتيجة داخل المُتغيّر joined_list، بعدها نحسب عدد أحرف النّتيجة المُتواجدة داخل المُتغيّر joined_list عبر استخدام الدّالة len التّي تقوم بحساب عدد عناصر سلسلة معيّنة في لغة بايثون، وفي الأخير نُرجع النّتيجة length التّي تُمثّل عدد عناصر السّلسلة joined_list، وهكذا فإنّ النّتيجة ستكون دائما عددا موجبا على شرط أن تكون القيمة value عبارة عن مجموعة من العناصر. المثال التّالي يُوضّح كيفيّة المُرشّح list_len الذي أنشأناه مع قائمة باسم numbers: {% set numbers = ['One', 'Two', 'Three'] %} {{ numbers | list_len }} النّتيجة ستكون العدد 11 وهو عدد أحرف الكلمات One، Two و Three مُجتمعة. مثال آخر: {% set words = ['Flask', 'Python', 'Hello', 'Javascript', 'Academy'] %} {{ words | list_len }} النّتيجة: 33 مُلاحظة: هذا المثال مُجرّد توضيح لكيفيّة إنشاء مُرشّح Jinja خاص بك وإن أردت تطبيق الفكرة في هذا المثال فيُمكنك الوصول إلى نفس النّتيجة عبر استعمال كل من المُرشّح join والمُرشّح length دون الحاجة إلى كتابة مُرشّح خاص بك. المثال التّالي يُوضّح كيفيّة الحصول على نفس النّتيجة باستعمال كل من join و length: {{ words | join("") | length }} كيفيّة إنشاء مُرشّح Jinja خاص بك، المثال الثّالث في هذا المثال، سنقوم بإنشاء مُرشّح لعرض صورة على المُتصفّح مُباشرة من رابطها، هذا يعني بأنّنا لن نحتاج إلى تمرير الرّابط كقيمة إلى الخاصيّة src في الوسم <img />. يجب أن نكون قادرين على استعمال المُرشّح كما يلي: image_link | image() | safe() سيتوجّب علينا استخدام المُرشّح safe لأنّ نتيجة المُرشّح image ستكون عبارة عن شيفرة HTML، ولتمكين المُتصفّح من ترجمة هذه الشّيفرة، لا بد من استخدام المُرشّح safe. ** مُلاحظة: ** استخدام المُرشّح safe خطير كما تعلم، لذا فيجب عليك أن تتأكّد من أنّ رابط الصّورة سليم وآمن قبل استخدام المُرشّح image عليه، من طرق التّأكد من أنّ الرّابط سليم هو تفادي منح المُستخدمين إمكانيّة إضافة روابط للصوّر، فإن أدخل مُستخدم ما شيفرة HTML وشيفرة Javascript عوضا عن رابط صورة فقد يتعرّض تطبيقك لهجمة خبيثة قد تُمكّن المُهاجم من الوصول إلى قاعدة بيانات التّطبيق أو سرقة حسابات مُستخدميك، لذا تأكّد من أنّ رابط الصّورة آمن قبل استعمال المُرشّح safe. في هذا المثال، يُمكن أن يكون المُرشّح بسيطا بحيث يعرض الصّورة المُتواجدة في الرّابط بأبعادها الأصليّة وبطريقة عاديّة، لكن يُمكننا أن نعمل على جعل المُرشّح أحسن باستعمال المُعاملات للتّحكم بكيفيّة عرض الصّورة، وللمزيد من المرونة، سنُضيف إمكانيّة التّحكم في حجم الصّورة عبر تمرير الحجم المرغوب إلى مُعامل باسم width أثناء تطبيق المُرشّح. لذا إن أردنا أن تكون الصّورة بحجم 200 px فسيتوجّب أن نكون قادرين على استخدام المُرشّح كما يلي: image_link | image(width=200) | safe بالإضافة إلى التّحكم بحجم الصّورة، سنُضيف كذلك إمكانيّة التّحكم بقيمة خاصيّة border-radius في لغة CSS لتحديد مدى استدارة حواف الصّورة. مع هذه الإضافة، المثال التّالي سيُنتج صورة بحجم 100 px وقيمة 5 px للخاصيّة border-radius أي أنّها ستكون مُستديرة الحواف قليلا: image_link | image(width=200, radius=5) بعد أن تعرّفنا على فكرة المُرشّح، لننتقل الآن إلى إنشائه. الشّيفرة التّالية تُمثّل شيفرة المُرشّح image مع إمكانيّة تحديد حجم الصّورة المعروضة وتحديد مدى استدارة حوافها: @app.template_filter('image') def image_filter(value, width=500, radius=0): css_style = "'width: {}px; border-radius: {}px'".format(width, radius) html_code = ''' <img src={} style={} /> '''.format(value, css_style) return html_code هناك العديد من الأمور التّي قد لا تُفهم في هذا المُرشّح، لذا لا بد من شرح مُفصّل. أولا، نقوم بإنشاء مُرشّح باسم image، الدّالة المسؤولة تأخذ ثلاثة مُعاملات، القيمة الأولى تُعطى للمُعامل الأول، وهي عبارة عن القيمة التّي يأخذها المُرشّح أثناء استدعائه (أي ما يسبق الرّمز |) والتّي ستكون رابط أو مسار الصّورة المرغوب عرضها، المُعامل width عبارة عن المُعامل الذي يُحدّد حجم الصّورة، إذا لم يتوفّر حجم مُحدّد أثناء استدعاء المُرشّح فالحجم الافتراضي هو 500. بالنّسبة للمُعامل radius فسيكون المُعامل الذي يُحدّد مدى استدارة حواف الصّورة، والقيمة الافتراضية هي 0 أي أنّ الصورة ستظهر بشكل عادي دون استدارة للحواف إذا لم تتوفّر قيمة أكبر من 0 للمُعامل radius أثناء استخدام المُرشّح على رابط الصّورة لعرضها. نُعرّف المُتغيّر css_style داخل الدّالة ليحمل شيفرة لغة CSS لتعديل الصّورة حسب قيم كل من الحجم و مدى استدارة الحواف التّي نحصل عليها من المُتغيّرين width و radius. ستُلاحظ بأنّنا نستخدم التّابع format لدمج قيم المُتغيّرين داخل السّلسلة النّصيّة، بهذه الطّريقة ستكون القيمة الافتراضيّة للمُتغيّر css_style ما يلي: 'width: 500px; border-radius: 0px' في السّطر التّالي، نُعرّف المُتغيّر html_code نقوم باستخدام خاصيّة تعدّد الأسطر في لغة بايثون عبر إحاطة شيفرة HTML بثلاثة علامات تنصيص ما يسمح لنا بتوزيع قيمة المُتغيّر على أكثر من سطر. في شيفرة HTML، نقوم باستخدام الوسم <img> لعرض الصورة عبر تمرير رابط أو مسار الصّورة إلى الخاصيّة src، كما نقوم بتمرير شيفرة CSS التّي تتواجد داخل المُتغيّر css_style إلى الخاصيّة style لتنسيق الصّورة حسب الحجم ومدى استدارة الحواف بلغة CSS. في الأخير نقوم بإرجاع شيفرة HTML المُتواجدة داخل المُتغيّر html_code والتّي ستكون مسؤولة عن عرض الصّورة. اختبار مُرشّح عرض الصّور بعد الانتهاء من إنشاء المُرشّح image لعرض الصّور، يُمكنك اختباره كما يلي: image_link | image(width=100) | safe مع التّأكد من أنّ المُتغيّر image_link عبارة عن رابط مُباشر للصورة. يُمكنك مثلا تجربة المُرشّح مع صورة رمزيّة لأحد دروس إطار العمل Flask السّابقة عبر تجربة السّطر التّالي: 'https://academy.hsoub.com/uploads/monthly_2016_05/flask-introduction.png.b896f4a662940d3b348b5b95f5eac86a.png' | image(width=100) | safe بعد تجربة المثال أعلاه، ستُلاحظ صورة درس "مدخل إلى تطوير تطبيقات الويب باستخدام إطار العمل Flask” بحجم 100px، ويُمكنك تغيير الحجم أو مدى استدارة الحواف كما تشاء. التّالي مثال على كيفيّة عرض نفس الصّورة بحجم أكبر وحواف أكثر استدارة: <center> <h1> إنشاء مُرشّحات Jinja خاصّة بك </h1> <br> {{ 'https://academy.hsoub.com/uploads/monthly_2016_05/flask-introduction.png.b896f4a662940d3b348b5b95f5eac86a.png' | image(width=300, radius=20) | safe }} </center> نتيجة المثال أعلاه ستكون مُشابهة لما يلي: خاتمة تعرّفنا في هذا الدّرس والدّروس السّابقة على كيفيّة استخدام مُرشّحات Jinja وكيفيّة إنشاء مُرشّحات خاصّة بك، لذا ستتمكّن الآن من استعمال ما تعلّمته لتطوير تطبيقات أكثر تعقيدا لتُلبي احتياجاتك وتُطبّق أفكارك بسهولة وطرق أحسن من ذي قبل. في الدّرس المُقبل، سنُنهي سلسلة الدّروس المُخصّصة لمُحرّك القوالب Jinja عبر التّعرف على مبدأ الاختبارات ومن ثمّ ننتقل إلى إكمال تطوير تطبيق "كلمة” في بقيّة الدّروس.
-
مُقدّمة تعرّفنا في الدّرس السّابق على بعض من مُرشّحات مُحرّك القوالب Jinja، مثل المُرشّح length لحساب عدد عناصر مجموعة ما، والمُرشّح list لتحويل القيم إلى قائمة، والمُرشّح replace لتعويض قيمة من نص ما بقيمة أخرى، وكذلك المُرشّح reverse لعكس قيمة نصيّة أو مجموعة من القيم، وانتهينا بالتّعرف على المُرشّح safe لإعلام Jinja بأنّ القيمة عبارة عن شيفرة HTML آمنة، في هذا الدّرس سنُكمل ما بدأناه بالتّعرف على المزيد من المُرشّحات المهمّة لننتقل بعد ذلك إلى كيفيّة إنشاء مُرشّح خاص بك مع مجموعة من الأمثلة في درس مُقبل. المُرشّح sort لترتيب قيمة قابلة للدّوران عليها يُمكنك استخدام المُرشّح sort لترتيب أي قيمة من النّوع Iterable، بمعنى آخر، أي قيمة يُمكنك الدّوران عليها والوصول إلى كل عنصر من عناصرها باستعمال حلقة for، وتشمل هذه القيم القوائم والمجموعات والسّلاسل النّصيّة وقيما أخرى. التّرتيب يكون من الأصغر إلى الأكبر بالنّسبة للأعداد، وبشكل أبجدي عند التّعامل مع السّلاسل النّصيّة. المثال التّالي يُوضّح كيفيّة ترتيب الأعداد من أصغر عدد إلى أكبر عدد: {{ "3241" | sort}} ستُلاحظ بأنّ النتيجة عبارة عن قائمة كما يلي: ['1', '2', '3', '4'] والمثال التّالي يوضّح كيفيّة استعمال المُرشّح مع سلسلة نصيّة لترتيبها أبجديّا: {{ "adbc" | sort}} النّتيجة ستكون عبارة عن قائمة أيضا: ['a', 'b', 'c', 'd'] وبما أنّ قائمة عاديّة عبارة عن Iterable كذلك، فيُمكنك استخدام المُرشّح sort مع القوائم بنفس الطّريقة: {{ ['c', 'b', 'd', 'a'] | sort() }} النّتيجة: ['a', 'b', 'c', 'd'] يُمكنك كذلك ترتيب كلمات كاملة: {{ ['Ali', 'Do', 'Flask', 'Ball'] | sort() }} النّتيجة: ['Ali', 'Ball', 'Do', 'Flask'] وعند جمع السّلاسل النّصيّة مع الأعداد فالنّتيجة ستبدأ أولا بالأعداد ثمّ السّلاسل النّصيّة بشكل مُرتّب ترتيبا أبجديّا: {{ '41532dabec' | sort() }} النّتيجة: ['1', '2', '3', '4', '5', 'a', 'b', 'c', 'd', 'e'] بعد بضعة تجارب، لاحظت بأنّ التّرتيب ممكن كذلك مع الكلمات العربيّة، ويُمكنك تجربة المثال التّالي لتتأكّد: {{ ['أب', 'جبل', 'ثلاثة', 'ترجم'] | sort() }} النّتيجة ستكون كما يلي: ['أب', 'ترجم', 'ثلاثة', 'جبل'] وبالطّبع، فإنّ أنسب استعمال لهذا المُرشّح يكون مع حلقة for لأنّ النّتيجة دائما ما تكون قائمة عاديّة، لذا سيتوجّب عليك الدّوران حولها لعرض كل عنصر على حدة. المُرشّح sum للحصول على جمع أعداد من مجموعة من القيم في بعض الأحيان، قد ترغب بجمع أعداد تتواجد بقائمة أو مجموعة عناصر عبارة عن أعداد مثل أسعار السّلع في قائمة أو عدد نقاط المُستخدم أو غير ذلك من الأعداد التّي سيُفيدك جمعها للحصول على قيمة إجماليّة. لنضرب مثالا لقائمة مُشتريات، كل عنصر يُمثّل سعر بضاعة ما، وسيتوجّب عليك عرض القيمة الإجماليّة لقائمة المُشتريات وذلك بجمع الأسعار. لنضع القائمة التّالية كمثال لأسعار ثلاثة سلع: [12, 14, 2] للوصول إلى السّعر الإجمالي، سيتوجّب عليك جمع كل من 12 مع 14 ثمّ إضافة 2 للنّتيجة. وهذا بالضّبط ما يقوم به المُرشّح sum. إليك المثال التّالي: {% set prices = [12, 14, 2] %} {{ prices | sum() }} النّتيجة ستكون العدد 28 وهي نتيجة العمليّة الحسابيّة 12 + 14 + 2. يُمكنك كذلك جمع الأعداد إن كانت مُتواجدة في خاصيّة، وذلك لأنّ سعر سلعة أو بضاعة مُعيّنة قد يكون داخل خاصيّة مثل price أو اسم آخر، لذا فللوصول إلى سعر سلعة ما سيتوجّب عليك القيام بذلك كما يلي: item.price لذا فطريقة استعمال المُرشّح sum في هذه الحالة مُختلفة قليلا، إذ سيتوجّب عليك تمرير اسم الخاصيّة إلى المُعامل attribute كما يلي: {{ items | sum(attribute='price') }} المثال التّالي سيقوم بالدّوران حول عناصر المتغيّر items والوصول إلى سعر كل عنصر عبر الخاصيّة price ومن ثمّ جمعها للحصول على السّعر الإجمالي. المُرشّح truncate لعرض جزء صغير من نص طويل إذا تصفّحت بعض المُدوّنات ومواقع الدّروس المكتوبة فستُلاحظ بأنّ الصّفحة التّي تعرض قائمة المقالات والدّروس تتميّز بكونها مُتناسقة بحيث تعرض جزءا صغيرا فقط من المقال الكامل لتتمكّن من الوصول إلى صفحته وقراءته كاملا عبر زر "اقرأ المزيد” أو ما شابه ذلك. يُمكننا أن نقوم بهذه المُهمّة ببساطة باستعمال المُرشّح truncate، كما يُمكنك التّحكم بحجم النّص المُختصر بكل سهولة. المثال التّالي يُوضّح كيفيّة استخدام المُرشّح مع نص طويل: {{ "مقال طويل يحتاج إلى تقصيره وعرض جزء صغير منه فقط ليكون تنسيق الصّفحة التّي تعرض عناوين المقالات أحسن" | truncate(length=50) }} النّتيجة: مقال طويل يحتاج إلى تقصيره وعرض جزء صغير منه ... يُمكنك تغيير طول النّص النّاتج بتغيير قيمة المُعامل length: {{ "مقال طويل يحتاج إلى تقصيره وعرض جزء صغير منه فقط ليكون تنسيق الصّفحة التّي تعرض عناوين المقالات أحسن" | truncate(length=25) }} هنا غيّرنا قيمة المُعامل length وجعلناها 25 عوضا عن 50، لذا فالنّتيجة ستكون هذه المرّة كما يلي: مقال طويل يحتاج إلى ... المُرشّح urlize لتحويل الروابط النّصيّة إلى روابط قابلة للضّغط عليها المُرشّح urlize مُرشّح مُفيد جدّا، إذ يقوم بتحويل أي رابط نصيّ إلى رابط يُمكنك الضّغط عليه للوصول إلى الموقع المُرتبط بذلك الرّابط. لتفهم أكثر، جرّب المثال التّالي: {{ "https://academy.hsoub.com/" }} النّتيجة مُتوقّعة، السّلسلة النّصيّة أعلاه ستظهر بشكل نص عادي ولا يُمكنك الضّغط عليها للوصول إلى موقع الأكاديميّة. الآن، حاول استعمال المُرشّح urlize مع النّص السّابق: {{ "https://academy.hsoub.com/" | urlize() }} ستُلاحظ بأنّ الرّابط في المُتصفّح أصبح مُختلفا وستتمكّن الآن من الضّغط عليه للوصول إلى موقع الأكاديميّة، وإن اطّلعت على مصدر الصّفحة فستُلاحظ ما يلي: <a href="https://academy.hsoub.com/">https://academy.hsoub.com/</a> ويُمكنك كذلك تجاهل الجزء http:// أو https:// ووضع رابط عادي كما يلي: {{ "academy.hsoub.com" | urlize() }} رغم ذلك الرّابط سيكون قابلا للضّغط عليه وسيوصلك إلى موقع الأكاديميّة. يُمكنك كذلك تقصير طول الرّابط الذي يظهر في المُتصفّح إلى طول تستطيع التّحكم به، وذلك بتمرير الطّول إلى المُرشّح كمُعامل كما يلي: {{ "https://academy.hsoub.com/tags/flask%20101/" | urlize(21) }} النّتيجة ستكون كما يلي: https://academy.hsoub... وعند النّقر على الرّابط ستتوجّه إلى صفحة سلسلة دروس Flask للمُبتدئين. المُميّز في هذا المُرشّح أنّه يُحوّل الرّوابط لتُصبح قابلة للضّغط عليها حتى ولو كانت مُدمجة داخل نص طويل يحتوي على كلمات أخرى لا علاقة لها بالرّابط، أي أنّه يُحوّل فقط الرّوابط داخل النّص وليس النّص بأكمله. إليك المثال التّالي لتتوضّح الفكرة: {{ " اضغط على الرّابط التّالي للوصول إلى سلسلة دروس إطار العمل فلاسك للمُبتدئين: https://academy.hsoub.com/tags/flask%20101/ أو تابع دروس بايثون عبر النّقر على الرّابط التّالي: https://academy.hsoub.com/programming/python/ " | urlize() }} ستُلاحظ بأنّ كلّا من الرّابطين يعملان بشكل صحيح وأنّ النّصوص الأخرى عاديّة. يُمكنك كذلك تقصير الرّوابط عبر تمرير طول إلى المُرشّح وسيُطبَّق على جميع الرّوابط، فمثلا نتيجة تمرير العدد 30 كمُعامل للمُرشّح urlize إلى المثال أعلاه هي كالتّالي: اضغط على الرّابط التّالي للوصول إلى سلسلة دروس إطار العمل فلاسك للمُبتدئين: https://academy.hsoub.com/tags... أو تابع دروس بايثون عبر النّقر على الرّابط التّالي: https://academy.hsoub.com/prog... المُرشّح wordcount لحساب عدد كلمات نص معيّن هذا المُرشّح لا يحتاج إلى الكثير من الشّرح، إذ أنّ فكرته بسيطة، إذا ما طبّقته على نص مُعيّن فستحصل على عدد الكلمات في النّص. مثال: {{ 'واحد اثنان ثلاثة' | wordcount() }} النّتيجة ستكون 3 لأنّ النّص أعلاه يحتوي على ثلاثة كلمات. المُرشّح wordwrap لنسيق النّصوص الطّويلة. المُرشّح wordwrap يقوم بتنسيق النّصوص الطّويلة بتقسيمها على عدّة أسطر، كل سطر لا يتعدّى 79 حرفا (يُمكنك تغيير هذا الطّول). إليك مثالا على كيفيّة استخدام المُرشّح مع نص طويل نفترض أنّه مُتواجد داخل المُتغيّر text: {{ text | wordwrap() }} لكن رغم تطبيق المُرشّح على النّص فالنّتيجة لن تظهر إلّا عند تصفّحك لمصدر الصّفحة، وذلك لأنّ المُرشّح لا يُقسّم الأسطر باستعمال الوسم <br> افتراضيّا، بل يجب عليك أن تقوم بتحديد نوع التّقسيم الذي تُريده أولا عبر تمرير الفاصل إلى المُعامل wrapstring. لذا للفصل بين الأسطر باستعمال الوسم <br> سيتوجّب علينا أن نُمرّره أولا إلى المُعامل wrapstring ثمّ تطبيق المُرشّح safe لإعلام Jinja بأنّ شيفرة HTML بداخل النّص آمنة (مُجدّدا، تأكّد أولا من أنّ النّص آمن بالفعل قبل تطبيق هذا المُرشّح). لذا فالمثال أعلاه سيكون كما يلي: {{ text | wordwrap(wrapstring='<br>') | safe() }} وكما قلت سابقا، فالمُرشّح يقوم بالفصل بين سطر وسطر آخر حسب طول مُحدّد، والقيمة الافتراضيّة لهذا الطّول هو 79 ويُمكنك تغييره إلى ما تشاء عبر تمرير القيمة التّي تُريدها إلى المُعامل width كما يلي: {{ text | wordwrap(width=20, wrapstring='<br>') | safe() }} المُرشّح select لعرض نتيجة حسب اختبار ما الاختبارات Tests في مُحرّك القوالب Jinja طريقة أخرى لمُعالجة القيم، وسنتطرّق إليها بشكل مُفصّل لاحقا، ما يجب أن تعرفه الآن هو أنّ المُرشّح select يُمكّنك من تطبيق اختبار على مجموعة من القيم لتُعرَض العناصر التّي تجتاز الاختبار وتُتجاهل العناصر الأخرى. سنُلقي نظرة على عدّة أمثلة لتفهم الأمر. أولا، سنُطبّق الاختبار odd على قائمة أعداد لاختيار الأعداد الفرديّة منها. المثال الأول: {% set numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9] %} {{ numbers | select("odd") | list() }} لاحظ بأنّنا استعملنا المُرشّح list على النّتيجة لتحويلها إلى قائمة وذلك لأنّها في الأصل كائن من النّوع Generator وهو نوع شبيه بالنّوع Iterator، أي أنّك تستطيع استخدام حلقة for عليه للوصول إلى كل عنصر على حدة، لكن لرؤية العناصر كاملة فسيتوجّب عليك تحويل النّتيجة إلى قائمة. النّتيجة: [1, 3, 5, 7, 9] لابدّ أنّك لاحظت بأنّ النّتيجة لا تحتوي سوى على الأعداد الفرديّة من القائمة numbers، وهذا لأنّنا طبّقنا عليها المُرشّح select والاختبار odd (الذي يختبر ما إذا كانت قيمة عددا فرديّا أم لا). المثال الثّاني: في المثال الثّاني سنستعمل الاختبار string الذي يتحقّق ما إذا كانت القيمة سلسلة نصيّة أو لا. في هذا المثال، سنُطبّق المُرشّح select مع الاختبار string على قائمة تحتوي على خليط من الأعداد والسّلاسل النّصيّة: {% set numbers = [1, 'Flask', 3, 42, 'Python', 'Hello World!', 2312, 2017, 'Jinja'] %} {{ numbers | select("string") | list() }} النّتيجة: ['Flask', 'Python', 'Hello World!', 'Jinja'] ستُلاحظ بأنّ الاختبار تجاهل جميع الأعداد لأنّها ليست سلاسل نصيّة، وفي المُقابل النّتيجة تحتوي على السّلاسل النّصيّة فقط. خاتمة تعرّفنا في الدّروس السّابقة وهذا الدّرس على كيفيّة التّعامل مع مُحرّك القوالب Jinja الذي يكون مُرفقا مع إطار العمل فلاسك، خاصّة كيفيّة العمل مع خاصيّة المُرشّحات والاستفادة منها، هذا الدّرس هو آخر درس يتحدّث عن المُرشّحات المبنيّة مُسبقا في Jinja ما يعني بأنّنا سنضطر إلى إنشاء مُرشّحات خاصّة بنا لتأدية أغراض أخرى غير التّي بُنيت لها المُرشّحات المبنيّة مُسبقا، لذا فالدّرس القادم سيكون عبارة عن مدخل إلى كيفيّة إنشاء مُرشّحات خاصّة بك مع مُساعدة من إطار العمل Flask للحصول على تجربة تطوير أفضل وتفادي تكرار شيفرات HTML.
-
مُقدّمة بعد أن تعرّفنا على جزء من أهمّ المُرشّحات التّي يُمكنك استعمالها لتفادي تكرار شيفرة عدّة مرّات والحصول على تجربة تطوير أحسن، سنُكمل في هذا الدّرس ما بدأناه بالتّعرفّ على قائمة مُرشّحات أخرى لتكون لديك فكرة أفضل عن كيفيّة استخدام مُحرّك القوالب Jinja والمُرشّحات المتواجدة به. المُرشّح length لقياس عدد عناصر مجموعة من القيم في لغة بايثون، يُمكنك استخدام الدّالة len لحساب عدد عناصر قائمة أو مجموعة من القيم. في مُحرّك القوالب Jinja، يُمكنك استخدام المُرشّح length للحصول على نفس النّتيجة. يُمكنك استخدام هذا المُرشّح كما يلي: {{ list | length }} مع استبدال المُتغيّر list بالمُتغيّر الذي يحمل القائمة التي ترغب بحساب عدد عناصرها. المثال التّالي عبارة عن توضيح لكيفيّة استعمال المُرشّح length للحصول على عدد عناصر القائمة comments لمنح المُستخدم فكرة عن عدد التّعليقات المُتوفّرة: {% set comments = ['Comment 1', 'Comment 2', 'Comment 3', 'Comment 4', 'Comment 5'] %} Comments({{ comments | length }}): {% for comment in comments %} <p> {{ comment }} </p> {% endfor %} إن جرّبت المثال في ملفّ filters.html فستُلاحظ نتيجة مُشابهة لما يلي: Comments(5): Comment 1 Comment 2 Comment 3 Comment 4 Comment 5 يُمكنك استخدام هذه الفكرة للإشارة إلى عدد التّعليقات في مقال مُعيّن (كما هو مُلاحظ في المثال أعلاه) أو الإشارة إلى عدد الرّسائل الجديدة أو عدد التّنبيهات أو أي مجموعة أخرى من القيم التّي سيُساهم الإشارة إلى عددها في تسهيل تجربة المُستخدم مع تطبيقك. مُلاحظة: يُمكنك كذلك استخدام المُرشّح count للحصول على نفس النّتيجة. المُرشّح list هذا المُرشّح يُحوّل قيمة ما إلى قائمة كما تفعل الدّالة list() في لغة بايثون. وإذا كانت القيمة عبارة عن سلسلة نصيّة فالنّتيجة ستكون عبارة عن قائمة من الحروف المتواجدة بالسّلسلة النّصيّة. المثال التّالي عبارة عن توضيح لكيفيّة تحويل الكلمة Hello إلى قائمة تحتوي على خمسة عناصر، كل عنصر منها يُمثّل حرفا من الكلمة Hello: {{ 'Hello' | list() }} ستكون النّتيجة كما يلي: ['H', 'e', 'l', 'l', 'o'] المُرشّح random المُرشّح random يختار عنصرا عشوائيّا من مجموعة قيم وتكون نتيجته مُختلفة في كل مرّة بدون ترتيب مُعيّن. يُمكنك استخدام هذا المُرشّح كما يلي: {{ ['One', 'Two', 'Three'] | random() }} إذا جرّبت المثال أعلاه في الملفّ filters.html وتوجّهت إلى العنوان http://127.0.0.1:5000/filters فستحصل على أحد عناصر القائمة أعلاه بشكل غير مُرتّب وغير مُتوقّع وكلّما أعدت تحميل الصّفحة ستتغيّر القيمة إلى قيمة أخرى وقد تحصل أحيانا على نفس القيمة أكثر من مرّة. يُمكنك استخدام هذا المُرشّح في تطبيقاتك لإضافة تفاعل أكثر إلى التّطبيق، فإن كان مثلا تطبيقك عبارة عن مُدوّنة فقد تعرض مقالا من المقالات بشكل عشوائي أو تعليقا من أحد المقالات أو غير ذلك. يُمكنك كذلك أن تعرض مقولة بشكل عشوائي في كل مرّة يُعاد فيها تحميل الصّفحة، والمثال التّالي يُوضّح هذه الفكرة: {% set quotes = ['Quote 1', 'Quote 2', 'Quote 3', 'Quote 4', 'Quote 5'] %} <div class="quote"> {{ quotes | random() }} </div> إذا ما قمت بتجريب هذا المثال فستحصل على أحد عناصر القائمة quotes في كلّ مرّة تُعيد فيها تحميل الصّفحة، و مع افتراض أنّ القائمة quotes تحتوي على مقولات شهيرة ومُميّزة فإنّ إضافة هذه الميّزة إلى مُدوّنتك أو تطبيق مُشابه سيُشكّل فرقا كبيرا. مُلاحظة: استعمال المُرشّح random يعني بأنّك ستقوم بجلب العديد من السّجلات من قاعدة البيانات لتُرجع سجّلا واحدا فقط على المُتصفّح للمُستخدم، ومُعظم قواعد البيانات من نوع SQL أو NoSQL تستطيع جلب سجل واحد فقط بشكل عشوائيّ، لذا فإنّ استعمال هذا المُرشّح في بعض الحالات غير مُجد، ومن المُفضّل استخدام قاعدة بياناتك للحصول على سجل بشكل عشوائيّ عوضا عن جلب جميع السّجلات وتطبيق المُرشّح random عليها. المُرشّح replace لتغيير قيمة إلى قيمة أخرى هذا مُرشّح آخر من المُرشّحات التّي استوحيت من لغة بايثون، إذ أنّ المُرشّح replace يعمل بنفس طريقة عمل التّابع replace في لغة بايثون، وذلك بأخذ قيمتين كمُعاملات، المُعامل الأول عبارة عن القيمة القديمة، والمُعامل الثّاني يُمثّل القيمة الجديدة. المثال التّالي يُوضّح كيفيّة عمل هذا المُرشّح: {{ 'Hello World' | replace('Hello', 'Hi') }} إذا قمت بتجربة المثال أعلاه فالنّتيجة ستكون Hi World بدل Hello World. يُمكنك كذلك استعمال المُرشّح replace مع مجموعة من القيم عوضا عن سلسلة نصيّة واحدة. المثال التّالي يُوضّح كيفيّة استخدام المُرشّح replace مع قائمة تحتوي على ثلاثة عناصر: {% set list = ['One', 'Two', 'Three'] %} {{ list | replace('One', 1) }} كما تُلاحظ، فقد استعملنا المُرشّح replace لاستبدال القيمة One بالقيمة 1، لذا فإنّ النّتيجة ستكون كالآتي: ['1', 'Two', 'Three'] المُرشّح reverse لعكس قيمة ما يُمكن أن تحتاج في بعض الأحيان إلى طريقة سريعة لعكس سلسلة نصّية. يُمكنك القيام بالأمر ببساطة باستعمال المُرشّح reverse كما في المثال التّالي: {{ "Hello World!" | reverse() }} ستُلاحظ بأنّ نتيجة المثال أعلاه هي كما يلي: !dlroW olleH أمّا بالنّسبة لمجموعات القيم مثل القوائم وغيرها فالأمر مُختلف قليلا، إذ أنّ تطبيق المُرشّح على قائمة سيُنتج كائنا من النّوع Iterator وهو نوع خاص يُمكنك استخدام حلقة for معه للوصول إلى كل عنصر على حدة وللوصول إلى جميع العناصر في آن واحد فسيتوجّب عليك تحويله إلى قائمة باستعمال المُرشّح list. لتتوضّح الصّورة، جرّب المثال التّالي في ملفّ filters.html: {% set list = ['One', 'Two', 'Three'] %} {{ list | reverse() }} ستُلاحظ نتيجة مُشابهة لما يلي: <list_reverseiterator object at 0x7fc0b6262518> لكن بعد استخدام المُرشّح list على النّتيجة كما هو مُوضّح في السّطر التّالي: {{ list | reverse() | list() }} فستحصل على النّتيجة التّاليّة: ['Three', 'Two', 'One'] وهي قائمة عناصرها مُرتّبة ترتيبا عكسيّا للقائمة list التّي عرّفناها من قبل: ['One', 'Two', 'Three'] مع التّأكيد على أنّك تستطيع الدّوران على نتيجة المُرشّح reverse بحلقة for دون الحاجة إلى تحويلها إلى قائمة بالمُرشّح list، بل من الأفضل الدّوران على نتيجة المُرشّح reverse مُباشرة لأنّ الدّوران عليها يتم بالوصول إلى كل عنصر على حدة عوضا عن تجميع كل عناصر قائمة ومن ثم الدّوران حولها. إليك مثالا على كيفيّة استخدام الحلقة for مُباشرة مع نتيجة المُرشّح reverse: {% set list = ['One', 'Two', 'Three'] %} <ul> {% for item in list | reverse() %} <li>{{ item }}</li> {% endfor %} </ul> النّتيجة: - Three - Two - One المُرشّح safe هذا المُرشّح مُهم ومُفيد جدّا، كما أنّ استعماله خطير كذلك، إذ يسمح لشيفرات HTML بالظّهور على أنّها آمنة، ما يعني بأنّها ستظهر في المُتصفّح كما كتبتها. لتوضيح الأمر، جرّب ما يلي: {{ "<h1>Hello World!</h1>" }} ستُلاحظ في المُتصفّح ما يلي بخط عادي: <h1>Hello World!</h1> هذا لأنّ مُحرّك القوالب Jinja لا يسمح بأن تُترجم شيفرات HTML مُباشرة إلى المُتصفّح لما في ذلك من أخطار أمنيّة. لكن إن كنت مُتأكّدا من أنّ شيفرة HTML المُتواجدة في مُتغيّر ما آمنة ولا تحتاج إلى إجراءات احتياطيّة، فيُمكنك استخدام المُرشّح safe لتُترجم الشّيفرة وتظهر على المُتصفّح بشكل عادي. لذا فالمثال التّالي سيُظهر الجملة Hello World! بخط كبير داخل الوسم <h1>: {{ "<h1>Hello World!</h1>" | safe }} المثال أعلاه قليل الورود لأنّ الهدف من المُرشّح safe هو إعلام مُحرّك القوالب Jinja بأنّ هذه القيمة عبارة عن شيفرة HTML آمنة، والقيمة عادة ما تكون داخل مُتغيّر أو على شكل عنصر من مجموعة عناصر. المثال التّالي يُوضّح كيفيّة استعمال المُرشّح safe لعرض قائمة تحتوي على شيفرات HTML: {% set list = ['<span div=list-item>One</span>', '<span div=list-item>Two</span>', '<span div=list-item>Three</span>'] %} <ul> {% for item in list %} <li>{{ item | safe }}</li> {% endfor %} </ul> كما تُلاحظ، فإنّ القائمة list تحتوي على عدّة عناصر، كل عنصر يحتوي على شيفرة HTML. لو قمنا بالدّوران حول القائمة وعرضنا كل عنصر دون استعمال المُرشّح safe، فستكون النّتيجة في المُتصفّح كما يلي: <span div=list-item>One</span> <span div=list-item>Two</span> <span div=list-item>Three</span> وهذه ليست النّتيجة التّي نُريدها، بل نُريد أن تُترجم الشّيفرة إلى لغة HTML يفهمها المُتصفّح. أمّا عند استعمال المُرشّح safe، فشيفرة HTML ستُترجم ليفهمها المُتصفّح وسيكون مصدر الصّفحة كما يلي: <ul> <li><span div=list-item>One</span></li> <li><span div=list-item>Two</span></li> <li><span div=list-item>Three</span></li> </ul> مُلاحظة: كن حذرا في استعمالك للمُرشّح safe وتأكّد أولا من أنّ شيفرة HTML التّي تُريد عرضها في المُتصفّح آمنة بشكل تام، فإن حدث وأن أسأت استعمال هذا المُرشّح فقد تفتح مجالا لهجمات XSS ما قد يُخرّب تطبيقك أو يسمح للمُهاجم بالوصول إلى قاعدة بيانات التّطبيق أو غير ذلك من الأعمال الخبيثة. خاتمة تعرّفنا في هذا الدّرس على المزيد من المُرشّحات التّي يُوفرّها مُحرّك القوالب Jinja لتتمكّن من تطوير قوالب HTML بشكل أسرع وبأقل تكرار ممكن للشّيفرة. لكنّنا لم ننته بعد، إذ لا تزال في جعبة مُحرّك القوالب Jinja المزيد من المُرشّحات المُفيدة والتّي سنتعرّف عليها في الدّرس المُقبل.
-
مُقدّمة بعد أن تعرّفنا على كيفيّة استخدام المُرشّحات في قوالب HTML، سنتعرّف في هذه الفقرة على بعض من أهم المُرشّحات المُتوفّرة في مُحرّك القوالب Jinja، والتّي يُمكنك استعمالها مُباشرة مع مشاريع Flask الخاصّة بك. المُرشّح default لتوفير قيمة افتراضيّة في بعض الأحيان، يُمكن أن يكون متغيّرٌ غير مُتوفّرٍ في القالب، لكن يُمكنك توفير قيمة افتراضيّة لتظهر في هذه الحالة. كمثال على ذلك، لنفترض بأنّك تعرض عنوان مقال باستخدام السّطر التّالي: <h1> {{ title }} </h1> في حالة لم يكن المُتغيّر title مُعرّفا فقد يُشوّه ذلك مظهر الصّفحة أو من المُمكن أن تحدث أخطاء غير مُتوقّعة، يُمكننا عوضا عن ذلك عرض النّص Title Not Found باستخدام السّطر التّالي عوضا عن السّطر السّابق: <h1> {{ title | default("Title Not Found") }} </h1> يُمكننا الآن التّأكّد من أنّ الجملة Title Not Found تظهر فقط في حالة لم يكن المُتغيّر title مُعرّفا، وذلك عبر تعريفه قبل استدعائه باستخدام السّطر التّالي: {% set title = "A title for a post" %} تأكّد فقط من تعريف المُتغيّر في سطر يسبق السّطر الذي تستدعي فيه قيمته. عند تعريف المُتغيّر ستظهر قيمته بشكل عادي، أمّا إن لم يكن مُعرّفا فالجملة “Title Not Found” ستظهر عوضا عن ذلك. المُرشّح capitalize لتحويل الحرف الأول من كلمة إلى حرف كبير في بعض اللغات اللاتينيّة، من المُهمّ أن تجعل الحرف الأول من بعض الأسماء حرفا كبيرا، فمثلا كتابة اسم على شكل Ali طريقة تعبير أفضل من كتابته على شكل ali. ولتحويل كل قيمة مُعيّنة إلى هذه الحالة يُمكننا استعمال المُرشّح capitalize كما يلي: <h1> {{ "academy" | capitalize() }} </h1> نتيجة المثال السّابق ستكون الكلمة academy مكتوبة على شكل Academy، لاحظ بأنّ الحرف الأول أصبح كبيرا وبقيّة الأحرف عاديّة. المُرشّح title لتحويل قيمة نصيّة إلى طريقة كتابة العناوين لا شك أنّك لاحظت بأنّ مواقع الأخبار الانجليزيّة والمُدوّنات تكتب عناوين مقالاتها بحيث يكون الحرف الأول من كل كلمة حرفا كبيرا. فعوضا عن كتابة عنوان بالطّريقة المُواليّة: how to use the flask framework to develop web applications فإنّ الطّريقة الصّحيحة هي بكتابته كما يلي: How To Use The Flask Framework To Develop Web Applications ولحسن الحظ، فإنّ مُحرّك القوالب Jinja يُسهّل علينا مهمّة تحويل جميع العناوين المُتواجدة في قاعدة البيانات إلى الشّكل الصّحيح للعناوين دون الاضطرار إلى تعديلها واحدا واحدا. يكفي استخدام المُرشّح title لتحويل أي عنوان كيف ما كان إلى الشّكل الصّحيح، والتّالي مثال على كيفيّة استخدام هذا المُرشّح: <h1> {{ "how to use the flask framework to develop web applications" | title() }} </h1> بالإضافة إلى كل من upper، capitalize و title فالمُرشّح lower يقوم بتحويل أي سلسلة نصّيّة إلى أحرف صغيرة. المُرشّح first لعرض أول عنصر من مجموعة عناصر إذا كنت تتعامل مع مُتغيّر يحمل مجموعة من القيم كقائمة بايثون تحتوي على العديد من العناصر، فإنّك تستطيع عرض أول عنصر دون العناصر الأخرى عبر استخدام المُرشّح first. المثال التّالي يُوضّح كيفيّة استخدام المُرشّح first: {% set list = ["One", "Two", "Three"] %} <h1> {{ list | first() }} </h1> في المثال أعلاه، نستخدم الكلمة المفتاحيّة set لتعريف مُتغيّر باسم list والذي يحمل بدوره قائمة من ثلاثة قيم، أمّا في السّطر الذي يليه، فنُطبّق المُرشّح first على القائمة list التّي أنشأناها قبل قليل. إذا عدت إلى الصّفحة الخاصّة بالمُرشّحات الآن، فستُلاحظ بأنّ ما بداخل الوسم <h1> هو القيمة One فقط، وذلك لأنّها أول قيمة في مجموعة القيم المُتواجدة داخل القائمة list. المُرشّح float لتحويل الأعداد إلى أعداد عشريّة هذا المُرشّح يعمل بنفس طريقة عمل الدّالة float() في لغة بايثون، إذ يقوم بتحويل أي عدد مهما كان نوعه إلى عدد عشري. يُمكنك استخدامه كما يلي: {{ 10 | float() }} ستُلاحظ بأنّ العدد 10 قد حوّل ليُصبح على شكل 10.0. المُرشّح يعمل مع السّلاسل النّصيّة كذلك، لذا فالمثال التّالي سيقوم بإنتاج 10.0 كذلك: {{ "10" | float() }} إذا لم يكن بالمقدور تحويل القيمة إلى عدد عشري فستظهر القيمة 0.0 عوضا عن ذلك. يُمكنك تجربة القيمة الافتراضيّة بمُحاولة تحويل سلسلة نصيّة إلى عدد عشري بمثال مُشابه لما يلي: {{ "Hello Word" | float() }} ستُلاحظ بأنّ النّتيجة هي 0.0، وهي القيمة الافتراضية التّي تظهر إذا ما كانت القيمة غير قابلة للتّحويل إلى عدد عشري. يُمكنك تغيير القيمة الافتراضيّة 0.0 إلى أي قيمة أخرى عبر تمرير القيمة الافتراضيّة الجديدة إلى المُعامل default كما يلي: {{ "Hello Word" | float(default="Error: value cannot be converted into a floating point number") }} بعد هذا التّغيير، ستجد بأنّ نتيجة تحويل قيمة لا يُمكن تحويلها إلى عدد عشري هي الجملة “Error: value cannot be converted into a floating point number” ويُمكنك تغيير هذه الرّسالة كيفما تشاء. المُرشّح int لتحويل القيم إلى أعداد صحيحة يعمل هذا المٌرشّح بطريقة مُشابهة لكيفيّة عمل المُرشّحfloat، إذ أنّ المُرشّح int يُحوّل أي قيمة إلى عدد صحيح، ويُمكنك استخدامه كما يلي: {{ 10.0 | int() }} وكما الحال مع المُرشّحfloat، فإنّ المُرشّح int يُحوّل أي قيمة غير قابلة إلى التّحويل إلى عدد صحيح إلى القيمة 0، ويُمكنك تعديل هذه القيمة الافتراضيّة عبر تمرير القيمة الجديدة إلى المُعامل default كما يلي: {{ "Hello Word" | int(default="Error: value cannot be converted into an integer number") }} المُرشّح join لضمّ عناصر مجموعة من القيم وجمعها لتكون قيمة واحدة في لغة بايثون البرمجيّة، يُمكننا جمع عناصر قائمة أو مجموعة من السّلاسل النّصيّة لتصبح سلسلة نصيّة واحدة باستخدام التّابع join. في مُحرّك القوالب Jinja، يُمكننا استخدام المُرشّح join للوصول إلى نفس النّتيجة. يُمكنك استخدام المُرشّح join كما يلي: {{ [1, 2, 3] | join() }} ستُلاحظ بأنّ النّتيجة في المُتصفّح هي 123. إليك مثالا آخر: {{ ["One", "Two", "Three"] | join() }} هذه المرّة ستكون النّتيجة القيمة OneTwoThree. يُمكنك كذلك الفصل بين العناصر بفاصل عبر تمريره إلى المُرشّح كمُعامل. المثال الأول: {{ [1, 2, 3] | join('|') }} في هذا المثال، ستُلاحظ بأنّ النّتيجة هي 1|2|3 عوضا عن 123 لأنّنا وضعنا فاصلا بين عناصر القائمة. المثال الثّاني: {{ ["One", "Two", "Three"] | join("-") }} هذه المرّة ستُلاحظ بأنّ النّتيجة هي One-Two-Three. المُرشّح last المُرشّح last يعمل بطريقة مُعاكسة للمُرشّح first، إذ أنّ هذا الأخير يعرض أول قيمة من مجموعة قيم، والمُرشّح last يعرض آخر قيمة من المجموعة. المثال التّالي يُوضّح كيفيّة استعمال المُرشّح last لعرض آخر قيمة من القائمة names: {% set names = ['Kamal', 'Ali', 'Ahmed', 'Khaled'] %} <h1> {{ names | last() }} </h1> إذا طبّقت المثال أعلاه، فسيتوجّب أن تحصل على الاسم Khaled كنتيجة لأنّه آخر عنصر من القائمة names. المثال التّالي يجمع كلّا من المُرشّح first والمُرشّح last: {% set names = ['Kamal', 'Ali', 'Ahmed', 'Khaled'] %} <h1> First: {{ names | first() }} </h1> <h1> Last: {{ names | last() }} </h1> النّتيجة ستكون كالتّالي: First: Kamal Last: Khaled ختاما تعرّفنا في هذا الدّرس على جزء من أهم مُرشّحات Jinja التّي يُمكنك أن تعتمد عليها في تطوير تطبيقات فلاسك الخاصّة بك، وسنستعمل بعضا منها في تطوير تطبيق “كلمة” في ما يلي من الدّروس، مُرشّحات Jinja كثيرة ولا يُمكن أن نذكرها جميعها في درس واحد، لذا فسنُكمل تغطيّة بقيّة المُرشّحات المُهمّة في دروس مُقبلة لنمرّ بعدها إلى جانب آخر من تطوير الويب أثناء تطويرنا لتطبيق كبير في هذه السّلسلة، لذا ترقّب بقيّة الدّروس.
-
مُقدّمة بعد أن تعرّفنا على كيفيّة العمل مع مكتبة 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، في هذا الدّرس سنُكمل هذا الجزء من تطوير الويب بتعلّم أساسيّات مُحرّك القوالب Jinja الذي يأتي مُرفقا مع إطار العمل فلاسك افتراضيّا، والذّي تُعتبر أساسيّاته أمرا مُهمّا لكونه يُستعمل في العديد من مشاريع بايثون أخرى بالإضافة إلى إطار العمل فلاسك. الدّوال في Jinja يُمكننا تعريف دوال لتحمل شيفرة معيّنة مع معاملات على Jinja لإعادة استعمالها وتحقيق مبدأ DRY أو Don’t Repeat Yourself وترجمته إلى اللغة العربيّة هي “لا تكرّر نفسك” ويعني بأنّه لا يجب عليك تكرار كتابة نفس الشّيفرة، وكلّما وجدت نفسك تُكرّرها توقّف وفكّر في إيجاد حل لتجنّب هذا التّكرار، إما بوضعها في دالّة أو شيء من هذا القبيل لتسهل عليك إعادة استعمالها. في Jinja مبدأ الدّوال موجود في ما يُسمى بالماكرو macro ويُمكن تعريف واحدة كما يلي: {% macro name(param1, param2) %} . . . {% endmacro %} كما تُلاحظ، الأمر شبيه بكيفيّة إنشاء الدّوال في لغة بايثون، لاحظ فقط كيف نُنهي الجزء الخاص بالدّالة بكلمة endmacro. يُمكننا استدعاء الماكرو في مكان آخر من القالب كما نعرض قيمة مُتغيّر معيّن: {{ name('pram1_value', 'pram2_value' }} يُمكننا كذلك تعريف الماكرو دون أية مُعاملات. ومن الشّائع أن توضع في ملف باسم _helpers.html في مجلّد templates الرّئيسي، وبعدها لاستخدامه يجب استيراده في أعلى الملف كما يلي: {% from "_helpers.html" import macro_name %} مع استبدال macro_name باسم الماكرو، تأكّد فقط بأنّك تستدعيه في أعلى الملفّ عوضا عن أي مكان آخر. بعد الاستدعاء ستتمكّن من استخدامه كالمُعتاد. مثال على ماكرو هناك العديد من الاستخدامات لهذه الخاصيّة، وسنتعرّف على بعض منها أثناء تطويرنا للتّطبيق، وهذا ما جعلني أشرح الفكرة هنا لتُفهم أكثر. في هذا المثال سنقوم بإنشاء ماكرو لتحويل قائمة بايثون إلى قائمة HTML. فمثلا لو كانت لدينا القائمة التّالية: list = ['Abdelhadi', 'Ayman', 'Ibrahim'] فسنستطيع تحويلها إلى التّالي بسطر واحد: <ul> <li>Abdelhadi</li> <li>Ayman</li> <li>Ibrahim</li> </ul> أولا سنُنشئ الماكرو كما يلي: {% macro py_to_html(list) %} <ul> {% for item in list %} <li>{{ item }}</li> {% endfor %} </ul> {% endmacro %} ثمّ نستدعيه كما يلي (على فرض أنّ list عبارة عن قائمة بايثون عاديّة): {{ py_to_html(list) }} ما ستُلاحظه هو أنّ النّتيجة ستكون عبارة عن قائمة HTML تحتوي على عناصر القائمة list، وهذا طبيعي لأنّ ما يفعله الماكرو ببساطة هو الدّوران حول المُعامل list الذي يستقبله ويعرض كل عنصر من القائمة داخل وسمي <li></li>. وهذه هي ببساطة طريقة عمل الماكرو، ويُمكنك إعادة استعماله أكثر من مرّة مع تمرير مُعاملات أخرى دون الحاجة إلى إعادة الشّيفرة مرارا وتكرارا. يُمكنك كذلك إنشاء ماكرو بدون أيّة مُعاملات، فمثلا الماكرو التّالي سيقوم بعرض الجملة “مرحبا بالعالم” في كل مرّة يتمّ استدعاؤه فيه: {% macro hello_world() %} مرحبا بالعالم {% endmacro %} وطريقة استدعائه في قالب HTML ستكون كالتّالي: {{ hello_world() }} سنتعرّف أثناء تطويرنا لتطبيق “كلمة” على المزيد من الأمثلة لكيفيّة استعمال ميّزة الماكرو في مُحرّك القوالب Jinja، وإن تابعت سلسلة استعمال مكتبة WTForms مع إطار العمل Flask فلا شك بأنّك ستأخذ فكرة واضحة عن كيفيّة عمل الماكرو كما ستحصل على مثال واقعي لاستخدامه. المُرشّحات (filters) في Jinja2 المُرشّحات مبدأ جديد يتوجّب عليك فهمه لكتابة تطبيقات ويب أفضل. يُطبّق المُرشّح في مُحرّك القوالب Jinja على مُتغيّر لتغيير قيمته من حالة إلى أخرى. التّالي مثال على كيفيّة عرض قيمة المُتغيّر name في مُحرّك القوالب Jinja: {{ name }} إذا ما طبّقنا على هذا المتغيّر مُرشّحا فسيتوجّب علينا أن نقسم بين المُتغيّر والمُرشّح بعلامة |. المثال التّالي يُوضّح كيفيّة تطبيق مُرشّح وهمي سنُسمّيه filter على المُتغيّر name: {{ name | filter }} نستعمل المُرشّحات كما نستعمل في لغة Python دوال تأخذ قيمة وتقوم بعمليّة أو عدّة عمليّات على هذه القيمة وتُرجع القيمة النّهائيّة التّي تُمثّل النّتيجة. إذا ما طبقت مُرشّحا على مُتغيّر ما، فقيمة المُتغيّر يجب أن تتغيّر، وسنرى بعض أهم المُرشّحات التّي يُوفرّها مُحرّك القوالب Jinja فيما بعد. يُمكنك كذلك أن تُمرّر مُعاملات إلى مُرشّح عبر تحديد القيم داخل قوسين كما يلي: {{ name | filter(argument1, argument2, argument3) }} المُعاملات التّي تُمرّر تُحدّد نوعيّة العمليّات التّي ستخضع لها القيمة قبل أن تُرجع النّتيجة. يُمكن كذلك استخدام أكثر من مُرشّح عبر تقسيمها بالرمز | كما في المثال التّالي: {{ name | filter1() | filter2() | filter3() }} كل مُرشّح يجري عمليّة على القيمة التّي تسبقه، لذا فطريقة عمل المثال أعلاه هو كالتّالي: يأخذ المُرشّح filter1 قيمة المُتغيّر name ويُرجع نتيجة تُعتبر مُجرّد قيمة أخرى. تمرّ النّتيجة السّابقة على المُرشّح filter2 لنحصل على قيمة جديدة تُعتبر نتيجة لعمل المُرشّح filter2 على ما أرجعه المرشّح filter1. يأخذ المُرشّح filter3 القيمة التّي نحصل عليها في النّتيجة السّابقة ويُجري عليها عمليّة ثمّ يُرجع قيمة جديدة. وبما أنّ آخر قيمة حصلنا عليها هي ما أنتجه المُرشّح filter3 على ما سبقه من قيمة، فما سيظهر للمُستخدم الذي يزور صفحة HTML التّي حدث بها ما حدث هو القيمة الأخيرة. ما فائدة المُرشّحات؟ رغم أنّ المُرشّحات في Jinja يُمكن أن تُستبدل بعمليّات على المُتغيّرات قبل تقديم قالب HTML عند العمل مع إطار العمل Flask، إلّا أنّ استعمال المُرشّحات حل أفضل وأكثر مرونة، وفي بعض الحالات يُمكن أن تحفظ نفسك من تكرار الكثير من الشّيفرة إن استعملت مُرشّحا يُوفّره مُحرّك القوالب Jinja عوضا عن كتابة الشّيفرة باستعمال لغة Python في كل مرّة تحتاج فيها إلى تغيير قيمة إلى قيمة أخرى. بالإضافة إلى أنّك تستطيع إنشاء مُرشّحات خاصّة بك لتستعملها مع إطار العمل Flask وقوالب Jinja إن أردت ذلك، فمثلا يُمكنك أن تكتب مُرشّحا لتحويل تاريخ مُعيّن من الصّيغة العاديّة (1-1-2017) إلى صيغة (قبل كذا يوم/ قبل كذا شهر/ منذ X أشهر …). سنرى كذلك بعض الاستعمالات للمُرشّحات فيما بعد، فمثلا، سنرى كيفيّة استعمال مُرشّح تُوفّره لنا إضافة Flask-Gravatar (سنرى المزيد عن إضافات Flask فيما بعد) لتحويل بريد إلكتروني إلى الصّورة المُرتبطة به على خدمة Gravatar. مثال على استخدام مُرشّح لتحويل نصّ إلى أحرف كبيرة كمثال على كيفيّة استخدام مُرشّح مُتوفّر مُسبقا بحيث يُمكنك استخدامه في أي قالب HTML في مشروع Flask الخاصّ بك، سننظرُ إلى كيفيّة استخدام المُرشّح upper الذي يعمل بنفس طريقة عمل التّابع upper() في لغة بايثون، والذي يُمكنك استخدامه لتحويل نصّ عادي إلى أحرف كبيرة Uppercase. أولا، سنُضيف مُوجّها جديدا إلى ملفّ __init__.py داخل مُجلّد المشروع project، يُمكنك اعتبار هذا المُوجّه حقل تجارب لتختبر به مُختلف المُرشّحات التّي سأسردها فيما بعد ومن المُفضّل حذفه إذا انتهيت منه. سيكون المُوجّه كالتّالي: @app.route('/filters') def filters(): return render_template('filters.html') المُوجّه بسيط جدّا، والعمل الحقيقي سيكون داخل الملفّ filters.html الذي قدّمناه كجواب في الموجّه. أنشئ الملفّ filters.html داخل المُجلّد templates المُتواجد في مُجلّد المشروع project. داخل الملفّ filters.html، أضف ما يلي: {% extends 'base.html' %} {% block title %} كلمة – المُرشّحات {% endblock %} {% block content %} <h1> {{ "Hello World!" | upper() }} </h1> {% endblock %} لاحظ الجزء {{ "Hello World!" | upper() }}، إن تابعت ما سبق جيّدا، فستفهم بأنّنا قُمنا في هذا الجزء بتطبيق المُرشّح upper على السّلسلة النّصيّة Hello World ، وبالطّبع، فإنّك تستطيع أن تُعوّض السّلسلة النّصيّة بمُتغيّر يحمل نفس القيمة أو قيمة أخرى. إن زرت العنوان http://127.0.0.1:5000/filters فستُلاحظ النّص HELLO WORLD!، ما يعني بأنّ المُرشّح upper قد أدّى مهمّته. استعمال المُرشّحات مع نصّ مُتعدّد الأسطر في الأمثلة السّابقة، تعرّفنا على كيفيّة استعمال مُرشّح في سطر واحد كما يلي: {{ "Hello World!" | upper() }} لكن ماذا لو أردنا استعمال المُرشّح في جزء أكبر من الشّيفرة؟ كاستعمال المُرشّح upper مع فقرة كاملة أو نص متعدّد الفقرات. يُمكننا استخدام المُرشّح كما نستعمل الجملة الشّرطية if أو حلقة for في Jinja، وذلك عبر إحاطة ما نُريد تطبيق المُرشّح عليه بكل من {% filter upper %} و {% endfilter %}، مع تغيير upper بالمُرشّح الذي تُريد استعماله. المثال التّالي يُمثّل كيفيّة استعمال المُرشّح upper مع نصّ مُتعدّد الأسطر: {% filter upper() %} Hello World! {% endfilter %} خاتمة تعرّفنا في هذا الدّرس كيفيّة استعمال خاصيّة الماكرو في مُحرّك القوالب Jinja لتفادي تكرار شيفرة عدّة مرّات، كما تعرّفنا على ماهيّة مُرشّحات Jinja وكيفيّة استعمالها والهدف منها، في الدّرس التّالي، سنلقي نظرة على بعض من أهمّ المُرشّحات التّي تأتي مبنيّة مُسبقا في مُحرّك القوالب Jinja والتّي يُمكنك استعمالها مُباشرة مع تطبيقات فلاسك الخاصّة بك.
-
مُقدّمة: بعد أن قمنا بتنظيم ملفّات التّطبيق على حسب الوظيفة باستعمال خاصيّة المُخطّطات، أصبح لدينا اختلاف بسيط في كيفيّة التّعامل مع ملفّات العرض التّي تحتوي على شيفرات HTML وشيفرات مُحرّك القوالب Jinja، وسيكون هناك اختلاف بسيط أيضا في طريقة استخدام الدّالة url_for للرّبط بين المُوجّهات. إنشاء صفحات HTML بما أنّنا أنشأنا ثلاثة مجلّدات باسم templates لتعدّد أغراضها، فسيتوجّب علينا أن نفهم أولا كل مجلّد ووظيفته. مسارات المُجلّدات التّي أنشأناها سابقا هي كالآتي: project/templates/ project/posts/templates/ project/users/templates/ المسار الأول عبارة عن مجلّد رئيسي للقوالب، يُمكنك أن تعتبره مُشتركا مع القوالب الأخرى، فمثلا يُمكن لكل من ملفّات المقالات والمُستخدمين أن يرثوا من ملفّ base.html كما سنوضّح بعد قليل، كما أنّ أي موجّه يُعرّف داخل الملفّ الرّئيسي project/__init__.py له الحق في تقديم الملفّات المُتواجدة في مجلّد القوالب الرّئيسي مُباشرة. أي أنّك تستطيع تعديل الموجّه الرّئيسي ليُقدّم القالب index.html المتواجد داخل مجلّد project/templates كالآتي: # project/__init__.py from flask import Flask, render_template #... #... @app.route('/') # 127.0.0.1:5000/ def index(): return render_template('index.html') # render project/templates/index.html لا تنس بأنّنا استردنا أولا الدّالة render_template ثمّ استدعيناها في المُوجّه لتقديم الملفّ index.html. بنفس الطّريقة سنقوم بتقديم كلّ من ملفّي posts.html الخاصّ بالمقالات و users.html الخاص بالمُستخدمين. حدّث المُوجّه في مُخطّط المقالات ليُصبح كما يلي: # project/posts/views.py from flask import Blueprint, render_template @posts.route('/') # 127.0.0.1:5000/posts def index(): return render_template('posts.html') # render project/posts/templates/posts.html وهكذا سيكون الموجّه الجديد في المُخطّطات الخاصّة بالمُستخدمين: # project/users/views.py from flask import Blueprint, render_template @users.route('/') # 127.0.0.1:5000/users def index(): return render_template('users.html') # render project/users/templates/users.html هكذا إن طلب الزّائر أي صفحة من الصّفحات الثّلاث فسيُقدّمُ ملفّ HTML المُناسب. استخدام إطار Bootsrap لتنسيق الصّفحات الهدف من هذه السّلسلة هو التّعريف بكيفيّة إنشاء تطبيقات ويب ديناميكيّة بلغة بايثون وإطار فلاسك ولن أتطرّق إلى لغات أساسيّة مثل HTML و css وJavascript، ولأنّ تطبيقات الويب في يومنا هذا أصبحت تعتمد كثيرا على المظهر الجميل والاستخدام المرن باستعمال كل من لغتي css وjavascript، فمن الضّروري أن نستعمل إطار عمل لسد هذا الفراغ وإتاحة تصميم أنيق لتطبيقنا. سنستخدم في هذا المشروع إطار bootsrap 3 الذي يُمكنك تحميله من الموقع الرّسمي وفك الضّغط عن ملفّ zip داخل مجلّد project/static، سأعتمد كذلك على مشروع Bootsrap-rtl لتعريب وتوجيه التّنسيق من اليمين إلى اليسار وسأضعه في مجلّد project/static/css كما سأضع مكتبة jquery داخل مجلّد project/static/css/bootstrap لذا تأكّد من أنّك قد جهّزت كلّ شيء قبل أن تبدأ في استدعاء الملفّات السّاكنة. بعد تجهيز مُجلّد static ستكون المُجلّدات والملفّات بداخله كما يلي: project/static/ ├── bootstrap │ ├── css │ │ └── bootstrap.min.css │ └── js │ ├── bootstrap.min.js │ └── jquery.js └── css ├── bootstrap-rtl.min.css └── style.css قد تكون هناك ملفّات إضافيّة حسب ما قُمت بتنزيله عندما نزّلت إطار العمل Bootstrap، لكنّ الملفّات أعلاه هي الأهم وهي التّي سنحتاج إليها حاليّا. إذا أردت إضافة ملفّات Javascript أخرى، فيُمكنك إمّا أن تضعها داخل مُجلّد js المُتواجد داخل المُجلّد bootstrap أو أن تُنشئ مُجلّدا جديدا باسم js داخل مُجلّد الملفّات السّاكنة static. الوراثة في قوالب Jinja2 لنقل بأنّنا نُريد عرض جملة واحدة في كل ملف من ملفات HTML الثلاثة. يُمكن أن أقوم بما يلي في كل ملفّ: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title></title> </head> <body> Hello </body> </html> مع تعويض Hello بالجملة التّي ارغب في عرضها عند طلب كل موجّه. هذه الطّريقة تعمل بشكل جيّد، لكن ألا تُلاحظ بأنّنا سنُكرّر نفس الأسطر في كلّ ملف؟ عوضا عن تكرار الأسطر في كلّ ملف يُمكننا استعمال مبدأ الوراثة في مجرّك القوالب Jinja. وللاستفادة من هذا المبدأ في المثال السّابق، يُمكن أن نكتب الأسطر التّي تتكرّر في كل ملف في ملفّ رئيسي واحد يُعتبر القاعدة لجميع الملفّات الأخرى، وهذا هو دور ملفّ project/templates/base.html الذي أنشأناه سابقا، ففيه يُمكننا وضع الأسطر السّابقة مع تخصيص المكان الذي سنستطيع كتابة الجملة الخاصّة بكل ملف فيه. افتح ملفّ base.html وضع فيه ما يلي: {# project/templates/base.html #} <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>{% block title %} {% endblock %}</title> <script src="{{ url_for("static", filename="bootstrap/js/jquery.js") }}"></script> <script src="{{ url_for("static", filename="bootstrap/js/bootstrap.min.js") }}"></script> <link rel="stylesheet" href="{{ url_for("static", filename="bootstrap/css/bootstrap.min.css") }}"> <link rel="stylesheet" href="{{ url_for("static", filename="css/style.css") }}"> <link rel="stylesheet" href="{{ url_for("static", filename="css/bootstrap-rtl.min.css") }}"> </head> <body> {% block content %} {% endblock %} </body> </html> احفظ الملف وأغلقه. لاحظ بأنّنا قمنا باستدعاء جميع الملفّات التّي سنحتاج إليها للتّنسيق وإضافة ميّزات أخرى بلغة Javascript، وقُمنا كذلك بوضع كتلة Block باسم title لتعويضها بالعنوان في كل ملفّ، أمّا كتلة content فهي التّي ستُعوّضُ بالمحتوى (كلمة Hello في مثالنا السّابق). يُمكنك إضافة ملفّات أخرى إلى مُجلّد static، لكن لا تنس أن تقوم باستيرادها في ملفّ base.html لكي تعمل في جميع الصّفحات. بعد تجهيز الملفّ base.html الذي سيُشكّل قاعدة للملفّات الأخرى، سنُنشئ الآن ثلاثة ملفّات HTML رئيسيّة، وهي كما يلي: ملفّ HTML للصّفحة الرّئيسية (مساره project/templates/index.html ). ملفّ HTML للصّفحة الرّئيسية للمُستخدمين (مساره project/users/templates/users.html). ملفّ المقالات الرّئيسي (مساره project/posts/templates/posts.html). لملء الملفّات، ضع الشّيفرة التّاليّة في كلّ ملفّ يُمكنك الاستعانة بالمسار المُشار إليه في التّعليق أعلى الشّيفرة للوصول إلى الملفّ بشكل أسرع. ملفّ الصّفحة الرّئيسية: {# project/templates/index.html #} {% extends 'base.html' %} {% block title %} كلمة – الصّفحة الرّئيسية {% endblock %} {% block content %} <h1> مرحبا بك في تطبيق كلمة </h1> {% endblock %} ملفّ الصّفحة الرّئيسية للمُستخدمين: {# project/users/templates/users.html #} {% extends 'base.html' %} {% block title %} كلمة – المُستخدمون {% endblock %} {% block content %} <h1> الصّفحة الرّئيسية للمُستخدمين </h1> {% endblock %} ملفّ الصّفحة الرّئيسية للمقالات: {# project/posts/templates/posts.html #} {% extends 'base.html' %} {% block title %} كلمة – المقالات {% endblock %} {% block content %} <h1> الصّفحة الرّئيسية للمقالات </h1> {% endblock %} كما ترى عوضا عن تكرار الكثير من شيفرات HTML قمنا ببساطة بإنشاء ملفّ واحد والوراثة منه باستخدام الجملة extends 'base.html' وفي كلّ مكان نضع المحتوى المناسب، وبهذه الطّريقة سنمتلك طريقة ديناميكية لتحديد عنوان ومحتوى كلّ موجّه مرتبط بقالب معيّن، بالإضافة إلى أنّ الملفّات السّاكنة مثل ملفّاتcss و js ستكون مُتاحة في جميع الملفّات التّي ترث من الملّف الرّئيسي base.html. مُلاحظة: في بعض المشاريع المكتوبة بلغة بايثون وإطار العمل فلاسك، يُمكن أن يُسمّى الملفّ المُشترك باسم layout.html عوضا عن base.html، لكنّ المبدأ هو نفسه. الآن إن عدت إلى الموجّهات السّابقة، فستُلاحظ بأنّ التّنسيق ومكوّنات كلّ صفحة قد تغيّرت. الرّبط بين المُوجّهات باستخدام الدّالة url_for بعد أن تعرّفنا على كيفيّة تقديم قوالب HTML، أصبح بإمكاننا عرض صفحات للمُستخدم حسب المُوجّه المطلوب، وبقي لنا التّعرف على كيفيّة الرّبط بين هذه الموجّهات. للرّبط بين موجّهات المُخطّطات باستخدام الدّالة url_for، نقوم أولا بوضع اسم المُخطّط ثمّ اسم الدّالة التّي يتم استدعاؤها عند الوصول إلى المُوجّه، وبما أنّنا سمينا هذه الدّوال باسم index فللرّبط بينها يُمكنك أن تقوم بشيء مُشابه لما يلي: url_for('index') # / url_for('posts.index') # /posts url_for('users.index') # /users لو كانت الدّالة تقبل مُعاملا لتوجّب علينا تمرير قيمته كالآتي: url_for('posts.post_by_id', post_id = 1) للرّبط بين الموجّهات الثّلاثة التّي سبق وأن أنشأناها سنُضيف ما يلي مُباشرة بعد وسم h1 في كل ملفّ من ملفّات HTML حسب المسار في بداية الشّيفرة. الرّوابط في الصّفحة الرّئيسية: {# project/templates/index.html #} <a href="{{ url_for("posts.index") }}"> اضغط هنا للوصول إلى صفحة المقالات </a> <br> <a href="{{ url_for("users.index") }}">اضغط هنا للوصول إلى صفحة المُستخدمين</a> رابط العودة إلى الصّفحة الرّئيسية في صفحة المقالات: {# project/posts/templates/posts.html #} <a href="{{ url_for("index") }}">اضغط هنا للعودة إلى الصّفحة الرّئيسيّة</a> رابط العودة إلى الصّفحة الرّئيسية في صفحة المُستخدمين: {# project/users/templates/users.html #} <a href="{{ url_for("index") }}">اضغط هنا للعودة إلى الصّفحة الرّئيسيّة</a> بعد حفظ الملفّات، ستجد بأنّ الصّفحة الرّئيسية أصبحت تحتوي على رابط لكلّ من صفحتي المقالات والمُستخدمين مع رابط للعودة إلى الصّفحة الرّئيسية في كل صفحة من الصّفحتين. الرّبط بين المُوجّهات مع تمرير مُعاملات للدالّة التّابعة لكل موجّه. لفهم مبدأ المعاملات أكثر، سنقوم بإضافة موجّه للوصول إلى مقال برقم مُعرّفه في ملفّ project/posts/views.py كما يلي: # project/posts/views.py #.. #.. @posts.route('/<int:id>') def post_by_id(id): return render_template('post.html', id = id) لاحظ بأنّنا خصّصنا المعامل id من نوع int أي عدد صحيح، فإن لم تكن قيمة المُعامل عند الوصول إلى الرّابط عددا صحيحا فإنّ ذلك سيُسبّب خطأ من نوع 404. بمعنى أدق، الرّابط /posts/1 صحيح، أمّا /posts/abc فسيرجع الخطأ ذو الرّقم 404. سنتعرّف على كيفيّة التّعامل مع هذه الأخطاء في درس قادم. سنضع ما يلي في ملفّ post.html الذي قدّمناه: {% extends 'base.html' %} {% block title %} كلمة – المقال ذو المُعرّف {{ id }} {% endblock %} {% block content %} <h1> صفحة المقال الواحد ذو رقم المُعرّف {{id}} </h1> <a href="{{ url_for("index") }}">اضغط هنا للعودة إلى الصّفحة الرّئيسيّة</a> {% endblock %} لنستخدم الآن الدّالة url_for داخل ملفّ posts.html لضرب مثال على كيفيّة تمرير مُعاملات إلى الدّالة. أضف ما يلي إلى ملفّ posts.html مُباشرة بعد الوسم <h1>: <a href="{{url_for("posts.post_by_id", id=1)}}"> <h2> رابط المقال الأول </h2> </a> <a href="{{url_for("posts.post_by_id", id=2)}}"> <h2> رابط المقال الثّاني </h2> </a> الآن، إن دخلت إلى صفحة المقالات فستلاحظ رابطين لمقالين وهميّين، الأول رقم معرّفه 1 والآخر رقم مُعرّفه 2، ورابط المقالين كالتّالي: http://127.0.0.1:5000/posts/1 http://127.0.0.1:5000/posts/2 يُمكنك كذلك زيارة صفحات أخرى عبر تغيير قيمة رقم المُعرّف إلى أي عدد صحيح، والتّالي بعض الأمثلة: http://127.0.0.1:5000/posts/99 http://127.0.0.1:5000/posts/232 في المقالات القادمة سوف نعوّض جملة “صفحة المقال ذي المُعرّف رقم * ” بعنوان المقال وسنعرض محتواه وقسمه أسفل العنوان مع الوسوم الخاصّة بالمقال، وذلك بجلب كل مقال حسب رقم مُعرّفه في قاعدة البيانات. خاتمة تعرّفنا في هذا الدّرس على جزء آخر من أساسيّات تطوير تطبيقات ويب أكثر تعقيدا وذات مهام مُتعدّدة، هذا الدّرس يُمثّل قسما صغيرا فقط ممّا يجب عليك معرفته حول كيفيّة التّعامل مع قوالب HTML ومحرّكها Jinja الذي يُرافق إطار العمل Flask افتراضيّا، لكنّك ستجده في مشاريع بايثون أخرى تتعلّق بتطوير الويب وبعض المجالات الأخرى ولو لم يكن إطار Flask حاضرا، لذا فتعلّم أساسيّات Jinja سيُخوّلك لتعلّم أساسيّات أطر عمل أخرى ومُختلف الأدوات التّي تعتمد عليه بشكل أسرع.
-
مُقدّمة بعد أن تعرّفنا على بنية التّطبيق الذي سنبنيه سويًّا باستخدام إطار العمل Flask ولغة Python، سننتقل الآن إلى العمل على تجهيز بيئة وهميّة لبايثون 3 لنقوم بتنصيب إطار Flask عليها ونبدأ بتطوير التّطبيق بمعزل عن النّظام المتواجد في الحاسوب، بعد تهيئة البيئة البرمجيّة، سنتعرّف على كيفيّة استغلال مبدأ المُخطّطات Blueprints التّي يُوفرّها لنا إطار العمل Flask لتوزيع أفضل لملفّات ومُوجّهات التّطبيق لعزل كل جزء وظيفيّ عن الأجزاء الأخرى وبالتّالي تسهيل تطوير خصائص وميّزات وكذلك تقسيم المُهمّات على أفراد الفريق البرمجي في حالة كنت تعمل على تطبيق مع أكثر من مُطوّر. إنشاء بيئة وهميّة بأداة Pyvenv أولا، سنحتاج إلى إنشاء بيئة وهميّة لعزل اعتماديات التّطبيق عن اعتماديات النّظام، وبما أنّ لديك خبرة في لغة بايثون، فلا بد بأنّك تُفكّر في استخدام أداة virtualenv، يُمكنك استخدامها إن أردت، لكنّنا سنستعمل بايثون 3 في تطويرنا لهذا التّطبيق، وإذا اتّبعت الدّرس الذي أشرت إليه في الدّرس السّابق، فستعلم بأنّه من المُفضّل استعمال أداة pyvenv للتأكّد من أنّ البيئة الوهميّة ستكون خاصّة بلغة بايثون 3 وليس بايثون 2. اتّبع الطّريقة التّقليديّة لإنشاء بيئة وهميّة باستعمال أداة pyvenv عبر السّطر التّالي: pyvenv venv إذا كنت تستخدم نظام Windows فقد لا يعمل السّطر أعلاه، لكن يُمكنك استخدام الأمر التّالي عوضا عنه: python -m venv فقط تأكّد من أنّ الأمر python يوصلك إلى مُفسّر بايثون 3 وليس مُفسّر بايثون 2. بعد انتهاء الأمر من التّنفيذ، قم بتفعيل البيئة الوهميّة بالسّطر التّالي: source venv/bin/activate بعد تفعيل البيئة الوهميّة، قم بتنصيب إطار العمل Flask باستخدام أداة pip بالسّطر التّالي: pip install Flask إذا كان السّطر ما قبل الأخير من المُخرجات كما يلي، فهذا يعني بأنّ كل شيء بخير: Successfully installed Flask itsdangerous Jinja2 Werkzeug click MarkupSafe طريقة العمل بما أنّنا أصبحنا نتعامل مع الكثير من الملفّات، عليك أن تعرف أي شيفرة ستكون في أي ملف، ولهذا سأقوم بوضع مسار الملف في أعلى الشّيفرة على شكل تعليق. وفي معظم الأحيان، لن أضع محتويات الملف كاملًا، بل سأضع الشيفرة الجديدة مُحاطة بسطرين قديمين من الأعلى والأسفل لتُدرك أي مكان يجب عليك أن تضع به الشّيفرة. مثال على الشّيفرة سيكون كالآتي: # project/users/views.py @users.route('/login') def login(): . . . login_user(user) return redirect(url_for('users.posts_by_author')) لا تقلق إن لم تفهم الأجزاء الجديدة عليك من الشّيفرة، فهي مُجرّد مثال بسيط على كيفيّة استخدام مُوجّه لمُخطّط وكيفيّة الرّبط بين الموجّهات. إنشاء المُخطّطات لدينا الآن مُخطّطان في تطبيقنا، واحد في مجلد المُستخدمين والآخر في مُجلّد المقالات. سنقوم أولا بإنشاء المُخطّط الخاصّ بالمُستخدمين في ملفّ views.py المتواجد داخل المُجلّد users كما يلي: # project/users/views.py from flask import Blueprint users = Blueprint('users', __name__, template_folder='templates', url_prefix='/users') في السّطر الأول، نستدعي Blueprint من حزمة flask، ونقوم بإنشاء كائن منها كما كنّا نقوم بإنشاء كائن app من الصّنف Flask في الماضي. المعامل الأول عبارة عن اسم المُخطّط، والمعامل الثّالث هدفه إخبار فلاسك بأنّ مُجلّد القوالب سيكون في نفس مُجلّد المُخطّط، وسيكون باسم templates ويُمكنك تغيير هذا الاسم إلى ما تشاء، لكنّنا لن نقوم بذلك، ومن المُفضّل اتّباع المُمارسات الشّائعة في مثل هذه الأمور. أمّا مُعامل url_prefix فالهدف منه وضع موجّه رئيسي قبل الموجّه المعرّف. مثلًا لتعريف الموجّه الرّئيسي / سيكون كالآتي: @users.route('/') # /users ولتعريف موجّه التّسجيل: @users.route('/register') # /users/register وكما تُلاحظ، لا أحتاج إلى تعريف /users في بداية كل مُوجّه. لو لم نُخصّص مُجلّدًا للقوالب، لكان علينا جمع جميع ملفّات HTML في مجلّد project/templates. هناك كذلك خيار لتفريق مجلّد الملفّات السّاكنة بنفس الطّريقة: users = Blueprint('users', __name__, template_folder='templates', static_folder='static', url_prefix='/users') ولأنّنا سنجمع جميع الملفّات السّاكنة في مُجلّد project/static فهذا الخيار لن يكون مُفيدًا لنا. يُمكنك الآن إنشاء المُخطّط الخاصّ بالمقالات بنفس الطّريقة. # project/posts/views.py from flask import Blueprint posts = Blueprint('posts', __name__, template_folder='templates', url_prefix='/posts') تسجيل المُخطّطات مع التّطبيق الرّئيسي بعد أن قُمنا بإنشاء مُخطّطات، حان الوقت لإخبار إطار فلاسك بأنّ هذه المُخّططات تابعة للتّطبيق، وطريقة القيام بذلك بسيطة. افتح الملف project/__init__.py وضع به ما يلي: # project/__init__.py from flask import Flask app = Flask(__name__) from project.posts.views import posts from project.users.views import users app.register_blueprint(posts) app.register_blueprint(users) @app.route('/') def index(): return 'Project Index' السّطران الأولان معروف بأنّهما لإنشاء التّطبيق، أمّا الاستدعاءات فهي لاستدعاء المُتغيّرين اللذين يحملان المُخطّطان، أمّا الأسطر الأخيرة فهي لتسجيل المُخطّطات التّي استدعيناها. أمّا الموجّه الذي عرّفته في الأسفل فهو ليتمكّن المُستخدم من الوصول إلى الصّفحة الرّئيسية دون الحصول على خطأ من نوع 404. ذلك لأنّ الموجّهات التّي سنعرّفها في المُخطّطات ستكون كما يلي: /posts /posts/new /users /users/register . . . لذا إن دخل المُستخدم إلى العنوان 127.0.0.1:5000 فلن يجد شيئا لأنّنا لم نُحدّد الموجّه الرّئيسي /، وهذا هو السّبب الذي جعلنا نُضيف المُوجّه بملفّ __init__.py في مجلّد المشروع project. إنشاء مُوجّهين رئيسيّين قلنا سابقًا بأنّ المُوجّهات ستكون داخل ملفّات views.py فإن أردنا أن نعرّف مثلًا موجّهًا للمقالات الرّئيسية، فسنقوم بذلك في ملّف posts/views.py أمّا الخاص بالمُستخدمين فسيكون في ملفّ users/views.py وهكذا. سنقوم الآن بإنشاء مُوجّه رئيسي لكل مُخطّط. أولا موجّه /posts/ سيكون كما يلي (ضعه مُباشرة بعد سطر تعريف المُتغيّر posts): # project/posts/views.py @posts.route('/') # 127.0.0.1:5000/posts def index(): return 'Posts Index' أمّا المُوجّه الخاصّ بالمُستخدمين فسيكون كما يلي: # project/users/views.py @users.route('/') # 127.0.0.1:5000/users def index(): return 'Users Index' وبهذه التّغييرات سيُصبح الملفّ views.py الخاصّ بالمُستخدمين كما يلي: from flask import Blueprint users = Blueprint('users', __name__, template_folder='templates', url_prefix='/users') @users.route('/') # 127.0.0.1:5000/users def index(): return 'Users Index' وملفّ views.py الخاص بالمقالات سيكون كالآتي: from flask import Blueprint posts = Blueprint('posts', __name__, template_folder='templates', url_prefix='/posts') @posts.route('/') # 127.0.0.1:5000/posts def index(): return 'Posts Index' تشغيل الخادوم بعد أن قمنا بتسجيل المُخطّطات وأنهينا أساسيّات التّطبيق، علينا أن نقوم بتشغيل الخادوم لنتمكّن من مُعاينة ما قُمنا به، ولتشغيل التّطبيق، سنضع السّطر المسؤول عن الأمر في ملفّ باسم run.py في مجلّد kalima. محتويات الملفّ ستكون كالتّالي: # run.py from project import app app.run(debug=True) كما تُلاحظ، استدعينا الكائن app الذي سبق وأن عرّفناه في ملفّ project/__init__.py بعدها نقوم بتشغيل الخادوم بالطّريقة المُعتادة مع خيار تصحيح الأخطاء. بعد حفظ ملفّ run.py يُمكنك تشغيله عبر سطر الأوامر: $ python run.py ستُلاحظ بأنّ الخادوم قد بدأ بالاشتغال كما العادة، لكنّ طريقة تشغيل الخادوم بهذه الطّريقة غير منصوح بها لأنّ النّسخة رقم 0.11 من Flask التّي أطلقت في أواخر عام 2016 جاءت بطريقة جديدة لتشغيل الخادوم عبر تنفيذ الأمر flask run. لكن الأمر flask run لن يعمل إلّا بعد تحديد اسم الحزمة أو الوحدة التّي يتواجد بها تطبيق Flask، وذلك عبر تعريف مُتغيّر بيئة باسم FLASK_APP وإعطائه القيمة حسب مكان تواجد تطبيق Flask الخاص بك. لتعريف مُتغيّر البيئة يُمكنك تنفيذ الأمر التّالي من الطّرفيّة بعد التّأكد من أنّ البيئة الوهميّة لديك مُفعّلة: $ export FLASK_APP=app.py في نظام Windows يجب عليك استخدام الكلمة المفتاحيّة set عوضا عن export: > set FLASK_APP=app.py في المثال أعلاه، نفترض بأنّ شيفرة تطبيق Flask مُتواجدة في الملفّ app.py. بعد تعريف مُتغيّر البيئة يُمكنك تشغيل الخادوم عبر تنفيذ الأمر التّالي: flask run عوضًا عن الأمر: python app.py في مشروعنا، تطبيق Flask مُتواجد داخل ملفّ __init__.py في مُجلّد project لذا فطريقة تعيين مُتغيّر البيئة سيكون كالتّالي: $ export FLASK_APP=project/__init__.py بعد تحديد مُتغيّر البيئة، يُمكنك تجربة تشغيل الخادوم: flask run بعد تنفيذك للأمر أعلاه، يجب أن تحصل على مُخرج كما يلي: * Serving Flask app "project" * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) لتفعيل مُصحّح الأخطاء، كل ما عليك فعله هو تعريف مُتغيّر البيئة FLASK_DEBUG وتعيين القيمة 1 له: export FLASK_DEBUG=1 مُلاحظة: أذكّر بأنّ تفعيل مُصحّح الأخطاء في بيئة الإنتاج أمر خطير، ولا يجب عليك أبدًا أن تقوم بذلك حتى ولو كنت مُتأكّدًا من أنّ تطبيقك خال من الأخطاء، وعليك أن تُدرك بأنّ مُصحّح الأخطاء مصمّم لمُساعدتك على تطوير واختبار التّطبيق وليس لتفعيله بعد نشر التّطبيق على الأنترنت. ومُجدّدًا، استبدل export بالكلمة set إذا كنت تستخدم نظام Windows. بعد تشغيل الخادوم مع وضع تصحيح الأخطاء، فسيكون المُخرج مُشابها لما يلي: * Serving Flask app "project" * Forcing debug mode on * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) * Restarting with stat * Debugger is active! * Debugger pin code: 573-756-626 وإن أردت أن يصل الآخرون في شبكتك إلى موقعك، تستطيع إضافة --host=0.0.0.0 إلى أمر تشغيل الخادوم كما يلي: flask run --host=0.0.0.0 بعد تشغيل الخادوم ستتمكّن من الوصول إلى الموجّهات الثلاثة التّي سبق وأن عرّفناها. 127.0.0.1:5000/ 127.0.0.1:5000/posts 127.0.0.1:5000/users وستُلاحظ النّتائج: Project Index Posts Index Users Index خاتمة تعرّفنا في الدّرس على كيفيّة إنشاء مُخطّطات وتسجيلها لمرونة أكثر في التّطوير ولإدارة أفضل للتّطبيقات الكبيرة، وتعرّفنا كذلك على كيفيّة تشغيله وكيفيّة إضافة مُوجّه لكل مُخطّط، في الدّرس القادم سنتعرّف على كيفيّة التّعامل مع قوالب HTML وكيفيّة الرّبط بين مُختلف موجّهات التّطبيق.
-
مقدّمة بعد أن تعرّفنا على بنية التّطبيق الذي سنبنيه سويا، سنبدأ بتهيئة وتنصيب بعض الحزم التّي سنعتمد عليها، وسنرى كيف يُمكننا تنظيم الملفّات لمرونة أكثر في التّعامل مع المشاريع الكبيرة، فعوضا عن وضع الشيفرة كاملة في ملفّ أو ملفّين، سنقوم باستعمال مبدأ المُخطّطات Blueprints الذي يوفّره لنا إطار العمل فلاسك لتنظيم التّطبيقات الأكثر تعقيدا. بنية التّطبيق المُعتادة سابقا، كنّا نستعمل ملفّا رئيسيّا واحدا ونقوم بتشغيله ليعمل الخادوم ونتمكّن من الوصول إلى التّطبيق عبر المُتصفّح من العنوان المُعتاد، وهكذا تكون جميع الموجّهات في ملفّ واحد، والملفّات السّاكنة في مجلّد واحد، وملفّات القوالب (ملفّات HTML) تكون جميعها في نفس المُجلّد. ملفّات التّطبيق المعتادة تكون على الشّكل التّالي: ├── app.py # ملفّ التّطبيق الرّئيسي ├── config.py # ملفّ الإعدادات ├── static # مجلّد الملفّات السّاكنة │ ├── css │ │ └── style.css │ ├── img │ │ └── logo.png │ └── js │ └── main.js ├── templates # مجلّد القوالب │ ├── index.html │ ├── posts.html مادام التّطبيق بسيطا فكلّ شيء سيكون على ما يرام، لكن ماذا لو كان التّطبيق يمتلك قسما لتسجيل المُستخدمين، عرض صفحاتهم الشّخصيّة، عرض المقالات، صفحات الأخطاء (مثل عدم تواجد صفحة أو خطأ بالخادوم …)، لوحة تحكّم للمُدير، لوحات التّحكم بأجزاء التّطبيق، واجهة برمجيّة للمُطوّرين API وغير ذلك من الخصائص التّي يتمتّع بها كل تطبيق كبير ذو وظائف مُتعدّدة. إن استعملت البنية المُشار إليها سابقا، ستواجه العديد من المشاكل، أولا سيكون الملفّ الرّئيسي مليئا بالموجّهات والدّوال، وقد يصل إلى آلاف الأسطر البرمجيّة ما سيجعل مهمّة الصّيانة وإضافة خصائص جديدة أمرا صعبا، كما أنّ ملفّات القوالب ستكون مجتمعة في مجلّد واحد، وإن كنت تعمل على تطوير التّطبيق مع فريق من المُطوّرين فسيجد كل شخص نفسك أمام ملفّات وشيفرات لا علاقة له بها، مثلا لو قُسّمت المهام بحيث يعمل كلّ مطوّر على قسم معيّن من التّطبيق، زيد هنا يعمل على نظام تسجيل للمُستخدمين، وعامر يعمل على إضافة قسم لعرض آخر التّعليقات المُضافة، وسعيد، مُصمّم الويب يعمل على تصميم صفحات أخطاء جميلة، وعبد القادر يعمل على لوحة تحكّم الإدارة، فكيف يُمكن لزيد أن يُعدّل الشيفرة التّي تقوم بتسجيل المُستخدمين دون المساس بالشّيفرة التّي تقوم بإعداد المقالات لعرضها على صفحة المقالات؟ المُخطّطات Blueprints المُخطّطات في فلاسك نظام لتجميع الموجّهات المُخصّصة لوظيفة مُعيّنة بعيدا عن المُوجّهات الأخرى، يُمكن كذلك تقسيم ملفّات القوالب والملفّات السّاكنة كذلك مع إتاحة خيار مُشاركة ملفّات مُحدّدة إن أردت ذلك. في هذا المشروع سنضع مجلّد قوالب لكلّ وظيفة، أمّا مُجلّد الملفّات السّاكنة فسيكون مُشتركا بين جميع القوالب، وهذا لأنّنا لن نحتاج إلى الكثير من ملفّات جافاسكريبت وملفّات css، وستكون الصّور مُشتركة كذلك. هناك نوعان معروفان من المُخطّطات، النّوع الأول يُقسّم ملفّات القوالب مع كلّ وظيفة ليكون كالتّالي: . ├── run.py ├── config.py ├── project │ ├── __init__.py │ ├── posts │ │ ├── __init__.py │ │ ├── templates │ │ │ ├── index.html │ │ │ ├── post.html │ │ │ ├── posts_by_category.html │ │ ├── views.py │ ├── static │ │ └── css │ │ └── style.css │ ├── templates │ │ ├── base.html │ └── users │ ├── __init__.py │ ├── templates │ │ ├── login.html │ │ └── register.html │ ├── views.py أمّا النّوع الثّاني فتجتمع فيه جميع ملفّات HTML في مُجلّد واحد كما يلي: . ├── run.py ├── config.py ├── project │ ├── __init__.py │ ├── posts │ │ ├── __init__.py │ │ ├── views.py │ └── users │ ├── __init__.py │ ├── views.py │ ├── templates │ │ ├── base.html │ │ ├── posts/ │ │ ├── users/ │ ├── static │ │ └── css │ │ └── style.css في المثالين السّابقين، لدينا مُخطّطان، مُخطّط خاصّ بالمقالات تحت مجلّد posts، وآخر خاصّ بالمُستخدمين في مجلّد users، ملفّ run.py مسؤول عن تشغيل التّطبيق وأمّا ملفّات __ini__.py فهي لجعل بايثون يفهم بأنّ كلّا من مجلّد المشروع project ومجلّدي المقالات والمُستخدمين عبارة عن حزم. أمّا ملفّا views.py فسيحتويان على الموجّهات المعنيّة بكلّ وظيفة، فمثلا مُوجّه عرض المقالات “/posts” سيكون في ملفّ posts/views.py أمّا موجّه تسجيل دخول المُستخدمين /login فسيكون في ملفّ users/views.py. لإخبار فلاسك بأنّ مجلّدي posts و users عبارة عن مُخطّطات، سنعرّف أولا كائنًا من صنف Blueprint في كل من ملفّات __init__.py في المُجلّدين، وبعدها سنقوم بتسجيلهما مع تطبيقنا الذي نُعرّفه في ملفّ __init__.py الأساسي -وهو المُتواجد في مجلّد project-. لك حريّة استخدام أي نمط تُريده، ففي النّهاية إطار فلاسك مُصمّم ليُوفّر لك كامل الحريّة في طريقة تنظيمك لملفّات التّطبيق، لكن اتّباع نمط مُتعارف عليه أفضل في مُعظم الحالات. سنستخدم في مشروعنا النّمط الأول، أي أنّ ملفّات HTML ستكون منفصلة حسب الوظيفة. بايثون 3 سنستعمل في إنشائنا لهذا التّطبيق النّسخة 3 من لغة بايثون، لن يتغيّر الكثير، فالاختلاف بين Python 2 و Python 3 بسيط جدا، وسأقوم بذكر أهم الاختلافات أثناء بناء التّطبيق في الدّروس القادمة. إذا كنت لا تزال تعتمد على بايثون 2 فأنصحك بأن تنتقل إلى بايثون 3 لأنّ الدّعم لهذه الأخيرة قد أصبح كبيرا ومُعظم مُطوري بايثون يستعملونها الآن، كما أنّها أحدث نُسخة. إذا أردت أن تتعلم كيفيّة تجهيز بايثون 3 على نظامك، يُمكنك اتّباع هذا الدّرس. تطبيق كلمة بما أنّ مشروعنا عبارة عن نظام لإدارة المُحتوى، قرّرت تسميّته “كلمة”، جميع الملفّات والمجلّدات الخاصّة بالتّطبيق ستكون بداخل مجلّد باسمkalima، لذا عليك إنشاؤه الآن في المكان المُفضّل لديك، المهم أن تعرف كيفيّة الوصول إليه عن طريق سطر الأوامر. في البداية، سننشئ الملفّات التّاليّة: . ├── run.py ├── project │ ├── __init__.py │ ├── posts │ │ ├── __init__.py │ │ ├── templates │ │ │ ├── posts.html │ │ ├── views.py │ ├── static │ │ └── css │ │ └── style.css │ ├── templates │ │ ├── base.html │ └── users │ ├── __init__.py │ ├── templates │ │ ├── users.html │ │ ├── login.html │ ├── views.py يُمكنك إمّا إنشاء المُجلّدات والملفّات يدويّا أو عبر نسخ الشيفرة التّالية وحفظها في ملفّ باسم create_app.py لإنشاء الملفّات والمُجلّدات باستعمال لغة بايثون تلقائيّا. import os import sys def mkapp(app_name, blueprints): dirs = ['{}', '{}/static', '{}/static/css', '{}/templates'] static_files = ['{s}/css/style.css'] templates_files = ['{t}/index.html', '{t}/base.html'] for d in dirs: os.mkdir(d.format(app_name)) open(app_name + '/' + "__init__.py", "w").close() # project/__init__.py for b in blueprints: os.mkdir(app_name + '/' + b) # project/posts os.mkdir(app_name + '/' + b + '/templates') # project/posts/templates open(app_name + '/' + b + '/' + "views.py", "w").close() # project/posts/views.py open(app_name + '/' + b + '/' + "__init__.py", "w").close() #project/posts/__init__.py open(app_name + '/' + b + '/templates/' + b + ".html", "w").close() # project/posts/templates/index.html for sf in static_files: static_file = app_name + '/' + sf.format(s='static') # project/static open(static_file, 'w').close() for tf in templates_files: templates_file = app_name + '/' + tf.format(t='templates') # project/templates open(templates_file, 'w').close() if __name__ == '__main__': app = sys.argv[1] blueprints = sys.argv[2:] mkapp(app, blueprints) يُمكنك تنفيذ السكربت بالأمر التّالي: $ python3 create_app.py project posts users بعدها أنشئ ملفّ login.html في مجلّد project/users/templates لأنّ السكربت لن يقوم بإنشائه. عليك كذلك إنشاء ملفّي run.py وconfig.py داخل مُجلّد kalima، الملفّ الأول مسؤول عن تشغيل الخادوم/ أمّا الملفّ الثّاني فمسؤول عن إعدادات التّطبيق. خاتمة تعرّفنا في هذا الدّرس على بنية التّطبيق الذي سنبنيه وكيفيّة توزيع ملفّاته إذ أنشأنا المُجلّدات والملفّات التّي ستكون مسؤولة عن الجانب العملي للتّطبيق، في الدّرس القادم، سنبدأ بتجهيز البيئة البرمجيّة وتنصيب أهم الحزم التّي سنحتاج إليها للبدء بتطوير التّطبيق.
-
مقدّمة بعد أن ألقينا نظرة على كيفيّة استخدام إطار العمل Flask لإنشاء تطبيقات ويب بسيطة بالاستعانة بقاعدة بيانات SQLite، سنبدأ هذه السّلسلة بالدّخول إلى عالم إضافات Flask التّي ستسهّل علينا الكثير من المهام كإعداد نظام لتسجيل الدّخول والخروج، والتّعامل مع نظام قاعدة بيانات، وكذلك التّحكم في تطوير التّطبيق عن طريق سطر الأوامر؛ وكذلك استعمال أدوات لتطوير تطبيق أكثر أمانا. سنتطرّق أيضا إلى بعض المفاهيم المتقدّمة كتشفير كلمات المرور وتهجير قواعد البيانات وغير ذلك. صحيح بأنّ هذه السّلسلة مطولة نوعا ما إلا أنّها ستكون شاملة لمُساعدتك على تطوير أي تطبيق يمر على خاطرك أو يطلبه منك أحد عملائك. وإن كنت جديدا إلى عالم تطوير الويب فستتعلّم من هذه السّلسلة ما يكفي لتبدأ مشوار العمل كمطوّر تطبيقات ويب سواء عن طريق إنجاز مشاريع لعملاء على منصّات العمل الحر كمنصّة مُستقل أو بناء مشاريعك الخاصّة. سنقوم في هذا الدّرس بالتّعرف على فكرة وبنية التّطبيق الذي سنبنيه في رحلتنا هذه. المُتطلّبات بالإضافة إلى أساسيّات إطار العمل Flask، ستحتاج كذلك إلى معرفة كيفيّة التّعامل مع نماذج HTML عبر مكتبة WTForms وإضافة Flask-WTF ويُمكنك الاطّلاع على سلسلة دروس خاصّة بهذا الموضوع على الأكاديميّة. سلاسل الدّروس التّي يجب عليك أن تطّلع عليها لفهم أفضل لهذه السّلسلة من الدّروس المُتقدّمة نوعا ما، عليك أن تطّلع وتطبّق سلاسل الدّروس التّاليّة: سلسلة مدخل إلى إطار العمل Flask درس إنشاء تطبيق لاختصار الرّوابط باستخدام إطار العمل Flask بجزئيه الأول و الثاني. سلسلة التّحقق من مُدخلات المُستخدم باستخدام مكتبة WTForms وإضافة Flask-WTF ستكون لغة جافاسكربت مُفيدة كذلك، ولا مفرّ من إتقانها إن كنت ترغب بأن تُصبح مُطور ويب أفضل، كما أنّني لن أشرح كل كبيرة وصغيرة عند التّعامل معها في التّطبيق، وهذا لأنّ الهدف من السّلسلة هو تعليمك كيفيّة إتقان التّعامل مع إطار العمل فلاسك وهذا بالطّبع لا يشمل شيفرة جافاسكربت. ستحتاج كذلك إلى تعلّم أداة Git لإدارة نُسَخ التّطبيق والتّعامل مع التّطبيق بعد نشره على منصّة Heroku. ما الذي ستتعلّمه من هذه السّلسلة؟ ستتعلم في هذه السّلسلة تقنيات متقدّمة لإدارة مشاريع تطبيقات الويب، وكيفيّة التّعامل مع قاعدة بيانات PostgreSQL وأداة SQLAlchemy لإدارة الجداول (إضافة، قراءة، تعديل وحذف البيانات)، وكذلك الطرق البسيطة لتهجير قواعد البيانات Database Migration وستتعرّف كذلك على طرق توزيع ملفّات التّطبيق على شكل وحدات وحزم لتنظيم أكثر. سنستخدم كذلك مكتبة WTForms لإدارة نماذج HTML وجعلها أكثر أمانًا للحماية من هجمات مُحتملة من المُخرّبين والمُخترقين. وسنعد كذلك نظامًا أكثر أمانًا لتسجيل الدّخول واستيثاق المُستخدمين، وسنتطرّق كذلك إلى كيفيّة نشر التّطبيق إلى منصّة Heroku لتربطه بعد ذلك باسم نطاق خاصّ إن أردت ذلك. ستتعلّم كذلك طريقة حماية كلمات المرور عن طريق تشفيرها في قاعدة البيانات باستعمال خوارزميّة Bcrypt للتّشفير، وذلك لتجنّب حفظها كنصّ مجرّد يسهل استخدامه من طرف المُخترق إذا وصل إلى قاعدة البيانات. وقد تعرّفنا سابقًا على كيفيّة استعمال الجلسات Sessions لإنشاء نظام تسجيل دخول وخروج بسيط، صحيح بأنّ ذلك النّظام يعمل بشكل جيّد، إلّا أنّه لا يعد آمنًا، ويُمكن أن يتعرّض تطبيقنا لهجمات كثيرة من قبل المُخترقين، ولحماية العضويات والبيانات في الموقع، سيتوجّب علينا أن نُدير الجلسات بطريقة أكثر تعقيدًا، لكنّ هناك إضافة لإطار العمل فلاسك لتبسيط هذا الأمر ولن نحتاج إلى العمل على ذلك بأنفسنا، وبالتّالي فسيُصبح تطبيقنا أكثر أمانًا دون عناء كبير، وكلّ هذا وأكثر سنتعلّمه بتفصيل أكبر في الدّروس القادمة. بنية التّطبيق التّطبيق الذي سنعمل عليه في هذه السّلسلة من الدّروس عبارة عن نظام إدارة محتوى ومنصّة للتّدوين الجماعي، ستكون هناك بعض الميّزات الفريدة وستكون هناك ميّزات أخرى مفقودة، إذا سبق لك وأن تعاملت مع أحد أنظمة التّدوين الأخرى كمنصّة ووردبريس WordPress، Blogger أو Tumblr أو حتى منصّة medium فستتمكّن بنهاية هذه السّلسلة من التّخلي عن هذه المنصّات وإنشاء منصّة بسيطة خاصّة بك لتكتب عليها، أو تستطيع تعديل المشروع بعد الانتهاء منه لتُضيف ميّزات أخرى أو تُطبّق فكرة فريدة لتطرح مشروعًا على أرض الواقع لتتمكّن من الاستفادة منه ماديّا إن أردت ذلك. سيتمكّن مُستخدمو المنصّة من إنشاء حساب خاصّ بهم لينشروا مقالاتهم التّي يكتبونها، إضافة المقالات ستتمّ عن طريق كتابتها بتنسيق الماركداون Markdown ومن ثمّ نشرها، سيتمكّن كلّ مُستخدم من الحصول على رابط خاصّ بمقالاته لنشرها للآخرين، سيتمكّن الكاتب من إضافة وسوم لمقالاته، وكذلك إدراجها ضمن قسم معيّن، لكل مقالة قسم للتّعليقات ليتمكّن المُستخدمون الآخرون من التّعليق على مقال معيّن. سيكون للتّطبيق نكهة اجتماعيّة، بحيث سيتمكن المُستخدمون من مُتابعة بعضهم البعض، وكذلك إضافة مقالات إلى المُفضّلة والإعجاب بها لنتمكّن من فرزها وترتيبها حسب الأكثر إعجابًا، الأكثر نشاطًا (أكبر عدد من التعليقات) وبعدها ستتمكّن من إضافة خاصيّات أخرى بنفسك باتّباع نفس الطّريقة، مثلًا بعد الانتهاء من التّطبيق ستتمكّن من تطبيق فكرة ترتيب المقالات حسب الأكثر إعجابًا لإضافة خاصيّات أخرى (الأكثر شعبيّة حسب عدد التّعليقات، الأكثر زيارة...). صحيح بأنّنا سنبني تطبيقًا مُعقّدا وكبيرًا، إلّا أنّني لن أشرح كلّ شيء بتفصيل ممل، سأشرح بالتّفصيل فقط الأفكار الجديدة وما تراه لأوّل مرّة حتى لا تكون هذه السّلسلة أطول من اللازم، خاصّة وأنّ بعض الأساليب ستكون مُشابهة جدّا لبعضها البعض، فمثلاً طريقة السّماح للمُستخدم بإضافة مقال خاص به مُشابهة جدّا لطريقة فرز المقالات حسب قسم مُعيّن (لكل مُستخدم مقالاته ولكلّ قسم مقالاته كذلك). سنضع للتّطبيق بعض الميّزات المُفيدة كنظام أرشفة لتأريخ كلّ مقال حسب تاريخ إضافته، كما سيكون لتعليقات المقال نظام مُشابه، وسنستغل نفس الطّريقة لتخصيص تاريخ انضمام للمُستخدم. في النّهاية، تذكّر بأنّ هذه السّلسلة من الدّروس ستكون تطبيقيّة بالدّرجة الأولى، وستتعلّم فيها كيفيّة بدء مشاريعك التّطويريّة الخاصّة بك بلغة بايثون وإطار فلاسك، أمّا كيفيّة الاستفادة القصوى منها فمعروفة، عليك تطبيق ما تعلّمته وتختبر مع إضافة ميّزات ولو لم أذكرها، وإن أردت معرفة معمّقة عن أي شيء تحدّثت عنه دون أن أفصّل، يُمكنك دائما العودة إلى التّوثيق الرّسمي للأدوات والمكتبات والإضافات المعنيّة بالأمر، وإن كان لك سؤال معيّن فلا تتردّد في طرحه على قسم الأسئلة والأجوبة على الأكاديميّة. هناك المزيد الدّروس التّي ستندرج تحت هذه السّلسلة لن تُغطي فقط ما قرَأتَهُ للتّو، بل أكثر من ذلك، فهدفي من كتابة سلسلة الدّروس هذه هو تغطيّة الفراغ الموجود في الويب العربي، إذ أنّ التّوثيقات الرّسميّة للمكتبات وأطر العمل المُستعملة حاليّا عادة ما تكون مكتوبة باللغة الانجليزيّة فقط، لذا بتوفير سلسلة دروس كهذه ستمتلك أكبر قدر مُمكن من المُساعدة لتتمكّن من العمل على تطبيقاتك الخاصّة دون أن تضطر إلى اتّباع سلسلة دروس أخرى أو الاطّلاع على التّوثيقات الرّسميّة التّي عادة ما تكون مُفيدة أكثر إذا ما أردت أن تُنجز شيئًا بسيطًا، وعلى العكس من ذلك، فكتابة تطبيق كامل (كما في هذه السّلسلة) طريقة أفضل للتّعلم. ختاما هذا المقال عبارة عن مُقدّمة بسيطة لما سنخوضه في قادم الدّروس، في الدّرس القادم، ستبدأ المُتعة مع استكشاف كيفيّة التّعامل مع ملفّات ومُجلّدات التّطبيقات الكبيرة والمُعقّدة بطريقة بسيطة.
-
مُقدّمة بعد أن تعرّفنا على كيفيّة إنشاء بعض حقول 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 للتّحقق من أنّ ما يُرسله المُستخدم إلى الخادوم يُطابق صيغة أو شكلا مُعيّنا (كبريد إلكتروني أو مقطع نصي بطول مُحدّد).
-
مُقدّمة: بعد أن تعرّفنا على بعض الأساسيّات في ما يخص إنتاج نماذج HTML والتّحقق من المُدخلات باستخدام كل من مكتبة WTForms وإضافة Flask-WTF، وبعد أن فهمنا ماهيّة مكتبة WTForms وأساسيّات التّعامل معها لإنشاء النّماذج وكيفيّة معالجة البيانات بعد استقبالها، حان الوقت للتّعرف على بعض المفاهيم المُتقدّمة والاستخدامات الأخرى للمكتبة، وهذا يشمل بقيّة الحقول مثل حقول الاختيار وأزرار الاختيار والقيم المنطقيّة وبعضا من أهم الطّرق للتّحقق من مُدخلات المُستخدم. الحقول ومكوّنات النّماذج كما تعلم، فإنّ نماذج HTML قد تحتوي على عدّة أنواع من الحقول، مثل حقل كلمة مرور، حقل لنص قصير وحقل النّص الطّويل (وسم TextArea) وبعض المكوّنات الأخرى التّي تُمكّن المُستخدم من إرسال بيانات إلى الخادوم (لتُعالج أو تُرسل فيما بعد إلى قاعدة البيانات)، وبالطّبع فبما أنّنا نتعامل مع النّماذج من خلال مكتبة WTForms فمن المُهم أن تعرف كيفيّة إنشاء كل حقل وكيفيّة التّعامل معه من حيث تقديمه إلى صفحة HTML واستقبال المعلومات وغير ذلك، أمّا طريقة تقديمه بصفحة HTML فهي نفسها في الحقول التّي ذكرتها مُسبقا، أمّا حقل الاختيارات فتقديمها مُختلف قليلا، وسنتعرّف عليها في الدّرس القادم. حقل النّص القصير وكلمة المرور. سبق وأن استخدمنا حقلي input من نوع text و password، وهذان الحقلان يُسمّيان TextField و PasswordField على التّوالي في مكتبة WTForms. ويُمكننا تعريف كل حقل كما يلي: text = TextField('Text Label') password = PasswordField('Password') وبالطّبع فسنحتاج إلى استيرادها من مكتبة WTForms : from wtforms import TextField, PasswordField ولتقديمها في ملفّ HTML يُمكنك الاستعانة بمُحرّك القوالب Jinja وعرض كل حقل كالآتي: {{ form.text }} {{ form.password }} وللوصول إلى وسم label كل حقل: {{ form.text.label }} {{ form.password.label }} وللوصول إلى الأخطاء المُنتجة من المُصادقين فيُمكنك القيام بالتّأكّد أولا من أنها موجودة في field.errors مع استبدال field باسم الحقل ثمّ الدّوران على الأخطاء بطريقة مُشابهة لما يلي: {% if form.text.errors %} {% for error in form.text.errors %} {{ error }} {% endfor %} {% endif %} {% if form.password.errors %} {% for error in form.password.errors %} {{ error }} {% endfor %} {% endif %} حقل النّص الطّويل (مساحة النّص، Text Area) تعرّفنا على كيفيّة إنشاء حقل صغير ليتمكّن المُستخدم من إرسال سلاسل نصيّة قصيرة (مثل اسم المُستخدم أو بريده الإلكتروني)، وحان الوقت للتّعرف على كيفيّة إنشاء حقل نصي أكبر للنّصوص الطّويلة مثل المقالات والتّعليقات، وهذا الحقل معروف في لغة HTML بوسم TextArea. يُمكنك إنشاء حقل نصّي طويل باستخدام مكتبة WTForms عن طريق الصّنف TextAreaField، وبالطّبع فلا بد من استيراده بنفس الطّريقة، لنقوم كالعادة بتعريف مُتغيّر في الصّنف الذي يسترد من الصّنف FlaskForm الذي نسترده في حالتنا من إضافة Flask-WTF، والمثال التّالي عبارة عن نموذج لتمكين المُستخدم من إرسال مقال عن طريق إرسال عنوانه ومُحتواه، والمُحتوى في هذه الحالة هو الحقل الذي سيكون طويلا: from flask_wtf import FlaskForm from wtforms import TextField, TextAreaField class PostForm(FlaskForm): title = TextField('Title') content = TextAreaField('Content') وكما تُلاحظ أعلاه، فالأمر مُشابه تماما لما سبق، ولو أردت إضافة مُصادق فالطرّيقة هي نفسها، استرد المُصادق ثمّ مرّره كعنصر من قائمة إلى المُعامل validators. وبنفس الطّريقة السّابقة، نقوم بإنشاء كائن من الصّنف PostForm ونقوم بتمريره إلى ملف HTML. def form(): form = PostForm() if form.validate_on_submit(): title = form.title.data content = form.content.data return render_template('form.html', form=form) وفي ملفّ HTML نقوم بتقديم النّموذج بنفس الطّريقة المذكورة في الدّرس السّابق. وستُلاحظ بأنّنا نستطيع الوصول إلى محتويات الحقول بعد إرسال النّموذج بشكل طبيعي: form.title.data form.content.data حقل الأعداد الصّحيحة بالإضافة إلى الحقول الافتراضيّة التّي يُمكنك إنشاؤها بلغة HTML، فإنّ مكتبة WTForms تُوفّر حقولا أخرى لا يُمكن أن تقبل سوى أنواع معيّنة من البيانات لتسهيل الأمر على المُطوّر، ومن هذه الحقول نجد حقل الأعداد الصّحيحةIntegerField، وهو حقل لا يقبل سوى الأعداد الصّحيحة، وهو نفسه حقل النّصوص القصيرة إلّا أنّ مكتبة WTForms تقوم بعمل إضافي للتأكد من أنّ المُدخل عبارة عن عدد صحيح. هناك العديد من الاستخدامات المُمكنة لاستغلال هذا الحقل، إذ يُمكنك مثلا إنشاء حقل لتمكين المُستخدم من توفير عمره، وبالتّالي تتأكّد من أنّ المُدخل لن يكون سلسلة نصيّة عشوائيّة أو شيئا من هذا القبيل، وبالطّبع فتستطيع إضافة مُصادق آخر للتّأكّد من أنّ العمر لا يتعدى عددا مُعيّنا إن أردت (لمنع المُستخدمين من توفير أعمار خياليّة على سبيل المثال). إليك مثالا على كيفيّة إنشاء نموذج يحمل حقل الأعداد الصّحيحة: from flask_wtf import FlaskForm from wtforms import IntegerField class AgeForm(FlaskForm): age = IntegerField('Integer') إذا حاولت إرسال بيانات غير الأعداد الصّحيحة فستحصل على خطأ ولن يُرسل النّموذج إلى الخادوم، وبالتّالي ستتجنّب بعضا من مُحاولات التّخريب. حقل التّاريخ حقل التّاريخ DateField نوع آخر من الحقول التّي تُوفّرها لنا مكتبة WTForms والذي نستعمله للحصول على المزيد من المرونة عند منح المُستخدم إمكانيّة إدخال تاريخ إلى قاعدة البيانات، ويُمكّننا الحقل من استقبال بيانات تكون على شكل تاريخ، أمّا إن لم يكن المُدخل على شكل تاريخ فسيحصل المُستخدم على خطأ ولن تمر المُدخلات إلى الخادوم، ويترك لك الحقل حريّة تحديد شكل التّاريخ فمثلا يمكنك تحديد التّاريخ على شكل يوم-شهر-سنة أو سنة/شهر/سنة أو أي شكل تحدّده مع الفصل بينها بأي فاصل تُريده، وأي بيانات تُرسل عن طريق ذلك لن تمرّ إلّا لو طابقت المعايير المُحدّدة. وطريقة العمل بسيطة جدّا، إذ تقوم بتعريف مُتغيّر ليُمثّل الحقل DateField الذي نقوم باستيراده بنفس الطّريقة المذكورة سابقا: from flask_wtf import FlaskForm from wtforms import DateField class AgeForm(FlaskForm): age = DateField('Date') وطريقة تقديمه بصفحة HTML لا تتغيّر كذلك. وإن أردت تغيير شكل التّاريخ إلى صيغة أخرى مثل يوم/شهر/سنة فالطّريقة بسيطة جدّا، إذ تُوفّر شكلا للتّاريخ المقبول أثناء تعريف الحقل DateField عبر تمريره إلى المُعامل format، والقيمة الافتراضيّة له هي كالآتي: '%Y-%m-%d' Y تعني السّنة بصيغتها الكاملة (مثل 1998، 2010، 2016 …). m تعني الشّهر. D تعني اليوم. وبالتّالي فالصّيغة التّالية ستكون مقبولة: 2016-10-13 أمّا التّاريخ بالصّيغة التّاليّة فغير مقبولة: 13-10-2016 ولتوفير صيغتك الخاصّة، فتستطيع ذلك عن طريق تمرير قيمة إلى المُعامل format لتعويض الصّيغة الافتراضية. والتّالي مثال على كيفيّة إنشاء حقل يقبل تاريخا على شكل يوم/شهر/سنة: date = DateField('Date', format='%d/%m/%Y') وبالتّالي فعند إدخال التّاريخ على الشّكل التّالي فسيُقبل ويُرسل إلى الخادوم دون مشاكل: 13/10/2016 إن أردت تجاهل أحد مُكونات التّاريخ فيُمكنك ذلك، فمثلا يُمكنك الاعتماد على السّنة والشّهر فقط: date = DateField('Date', format='%Y-%m') وبالتّالي فسيكون التّاريخ المقبول كما يلي: 2016-10 مُعالجة التّاريخ بعد استقباله عندما يُدخل المُستخدم التّاريخ ثمّ يصل إلى الخادوم، فتستطيع الوصول إليه كما يلي: form.date.data وهذه هي الطّريقة التّقليديّة، ولكن، لأنّ WTForms تقوم بإنشاء تاريخ من النوع datetime.date في لغة بايثون، فسنستطيع الوصول إلى المعلومات الأخرى بسهولة. فمثلا، للوصول إلى السّنة في التّاريخ المُرسل، تستطيع بسهولة إضافة year إلى ما سبق: form.date.data.year وبنفس الطّريقة، تستطيع الوصول إلى الشّهر: form.date.data.month واليوم: form.date.data.day وكأي كائن من النّوع datetime.date فستتمكّن من الوصول إلى جميع المعلومات الأخرى المُتعلّقة بالتّاريخ. مُلاحظة: الحقل DateField يقوم بإنشاء حقل نص قصير عادي، إن أردت استخدام الحقل الجديد في HTML5 بالخاصيّة type="date"، فتستطيع استيراده من الحزمة html5 في مكتبة WTForms كما يلي: from wtforms.fields.html5 import DateField إن أنشأت حقلا من هذا الصّنف فستحصل على ما يلي: <input id="date" name="date" type="date"> لاحظ بأنّ قيمة الخاصيّة type قد تغيّرت من text إلى date وبالتّالي فستتمكّن من توفير تجربة استخدام أفضل عبر تمكين مُستخدمي تطبيقك من اختيار التّاريخ باستخدام الأداة المتوفّرة في لغة HTML5. خاتمة تعرّفنا في هذا الدّرس على بعض من المكوّنات الشّائعة في مكتبة WTForms التّي يُمكنك الاستفادة منها في تطبيقاتك لتوفير حماية أفضل، وبما أنّ هناك العديد من الحقول في لغة HTML، فسنتطرّق إلى ما تبقّى في الدّرس التّالي.
-
مُقدّمة تعرّفنا في الدّرسين السّابقين على بعض المفاهيم المُتعلّقة بإدارة نماذج HTML والتّحقق من مُدخلات المُستخدم باستخدام مكتبة WTForms، إذ تعرّفنا على كيفيّة إنشاء صنف يُمثّل نموذج HTML وعلى كيفيّة تقديمه إلى قالب باسم form.html، وقد حان الوقت لنستغلّ إمكانيّات مُحرّك القوالب Jinja لعرض النّموذج والحقول التّي فيه بملفّ HTML وبالتّالي فسيتمكّن المُستخدم من ملء النّموذج من مُتصّفحه وإرسال البيانات إلى الخادوم (تطبيق Flask الخاص بنا)، لنقوم بعد ذلك بالوصول إلى المُدخلات ومُعالجتها. أساسيّات عرض النّموذج ليتمكّن المُستخدم من إرسال البيانات إلى التّطبيق لنُعالجها لا بد من تقديم نموذج 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، وبالتّالي ففي كل مرّة يُرسل فيها مُستخدم بياناته فستُرسل البيانات في الحقول مع الحقل الخفي ثمّ يتم التّحقق منه. بعد تحديد الحقل الخفي، نقوم بعرض لصيقة كل حقل، ثمّ نقوم بعرض الحقل وبعدها نعرض الأخطاء التي تتعلّق به (أي الأخطاء التّي يُنتجها كل مُصادق). وفي الأخير نضيف زرا ليتمكّن المُستخدم من إرسال البيانات. وهكذا سيُصبح النّموذج كما في الصّورة التّالية: وإن حاولت إرسال النّموذج دون ملئ الحقول فستحصل على خطئ من المصادق DataRequired : وإن ملأت حقلا وتركت آخر، فالخطأ يظهر عند الحقل الفارغ فقط: يُمكنك كذلك النّظر إلى مصدر الصّفحة إن أردت تفحّص ما تُنتجه مكتبة 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 الأخرى مثل القوائم المُنسدلة ومربّعات الاختيار وغيرها، وسنتعرّف كذلك على مُصادقين آخرين لتوفير حماية أكثر لتطبيقاتنا.
-
مُقدّمة: بعد أن تعرّفنا على أهم المفاهيم الأساسيّة حول التّحقق من مُدخلات المُستخدم ومكتبة 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، لذا سنستغل هذا الكائن في الدّرس القادم لنتمكّن من عرض النّموذج على مُتصفّح المُستخدم، وسنعرّف كذلك على كيفيّة الوصول إلى البيانات وعلى كيفيّة مُعالجتها واستغلالها بعد أن يملأ المُستخدم النّموذج ويُرسله.