المحتوى عن 'flask_cms'.



مزيد من الخيارات

  • ابحث بالكلمات المفتاحية

    أضف وسومًا وافصل بينها بفواصل ","
  • ابحث باسم الكاتب

نوع المُحتوى


التصنيفات

  • التخطيط وسير العمل
  • التمويل
  • فريق العمل
  • دراسة حالات
  • نصائح وإرشادات
  • التعامل مع العملاء
  • التعهيد الخارجي
  • مقالات عامة
  • التجارة الإلكترونية

التصنيفات

  • PHP
    • Laravel
    • ووردبريس
  • جافاسكريبت
    • Node.js
    • jQuery
    • AngularJS
    • Cordova
  • HTML5
  • CSS
    • Sass
    • إطار عمل Bootstrap
  • SQL
  • سي شارب #C
    • منصة Xamarin
  • بايثون
    • Flask
    • Django
  • لغة روبي
    • إطار العمل Ruby on Rails
  • لغة Go
  • لغة جافا
  • لغة Kotlin
  • برمجة أندرويد
  • لغة R
  • سير العمل
    • Git
  • صناعة الألعاب
    • Unity3D
  • مقالات عامّة

التصنيفات

  • تجربة المستخدم
  • الرسوميات
    • إنكسكيب
    • أدوبي إليستريتور
    • كوريل درو
  • التصميم الجرافيكي
    • أدوبي فوتوشوب
    • أدوبي إن ديزاين
    • جيمب
  • التصميم ثلاثي الأبعاد
    • 3Ds Max
    • Blender
  • مقالات عامّة

التصنيفات

  • خواديم
    • الويب HTTP
    • قواعد البيانات
    • البريد الإلكتروني
    • DNS
    • Samba
  • الحوسبة السّحابية
    • Docker
  • إدارة الإعدادات والنّشر
    • Chef
    • Puppet
    • Ansible
  • لينكس
  • FreeBSD
  • حماية
    • الجدران النارية
    • VPN
    • SSH
  • مقالات عامة

التصنيفات

  • التسويق بالأداء
    • أدوات تحليل الزوار
  • تهيئة محركات البحث SEO
  • الشبكات الاجتماعية
  • التسويق بالبريد الالكتروني
  • التسويق الضمني
  • استسراع النمو
  • المبيعات

التصنيفات

  • إدارة مالية
  • الإنتاجية
  • تجارب
  • مشاريع جانبية
  • التعامل مع العملاء
  • الحفاظ على الصحة
  • التسويق الذاتي
  • مقالات عامة

التصنيفات

  • الإنتاجية وسير العمل
    • مايكروسوفت أوفيس
    • ليبر أوفيس
    • جوجل درايف
    • شيربوينت
    • Evernote
    • Trello
  • تطبيقات الويب
    • ووردبريس
    • ماجنتو
  • أندرويد
  • iOS
  • macOS
  • ويندوز

التصنيفات

  • شهادات سيسكو
    • CCNA
  • شهادات مايكروسوفت
  • شهادات Amazon Web Services
  • شهادات ريدهات
    • RHCSA
  • شهادات CompTIA
  • مقالات عامة

أسئلة وأجوبة

  • الأقسام
    • أسئلة ريادة الأعمال
    • أسئلة العمل الحر
    • أسئلة التسويق والمبيعات
    • أسئلة البرمجة
    • أسئلة التصميم
    • أسئلة DevOps
    • أسئلة البرامج والتطبيقات
    • أسئلة الشهادات المتخصصة

التصنيفات

  • ريادة الأعمال
  • العمل الحر
  • التسويق والمبيعات
  • البرمجة
  • التصميم
  • DevOps

تمّ العثور على 24 نتائج

  1. مُقدّمة بعد إنشاء جدولي المقالات والمُستخدمين في قاعدة البيانات في الدّرس السّابق، حان الوقت للتّعامل مع البيانات وإدارتها باستعمال لغة بايثون عوضا عن لغة SQL؛ في هذا الدّرس سنتعرّف على كيفيّة الوصول إلى مُفسّر بايثون في مجلّد مشروعنا وكذلك كيفيّة إضافة البيانات وجلبها من قاعدة البيانات. الوصول إلى مُفسّر بايثون داخل مُجلّد المشروع سنقوم في هذا الدّرس بالتّعامل مع قاعدة البيانات باستعمال Flask-SQLAlchemy مُباشرة من مُفسّر بايثون، إن كنت على نظام Windows فهو في الغالب متواجد في قائمة ابدأ، أمّا إن كنت على لينكس أو OSX فادخل إلى الطّرفيّة Terminal واكتب python تأكّد فقط من أنّك في مُجلّد kalima قبل أن تقوم بالوصول إلى مُفسّر بايثون، وتأكّد كذلك بأنّك قد فعّلت البيئة الوهميّة. إن لم تستطع الوصول إلى مُفسّر بايثون في مُجلّد kalima فيُمكنك تنفيذ الأمر chdir من وحدة os لتغيير المُجلّد الحالي إلى مسار مُجلّد kalima: >>> import os >>> os.chdir('path/to/kalima') مع تغيير path/to/kalima إلى مسار مُجلّد kalima في حاسوبك. مُلاحظة: الشّيفرة التّي سأقوم بتنفيذها ستُسبق بعلامة >>> أمّا المُخرج/النّتيجة فلن يُسبق بشيء. مثال: >>> site = 'Hsoub Academy' >>> site 'Hsoub Academy' بما أنّنا في مُفسّر بايثون نستطيع الوصول إلى قيمة المُتغيّر دون طباعتها، فقط اكتب اسم المُتغيّر ثم اضغط مفتاح ENTER. سنستعمل كلّا من db والصّنفين اللذ]ن يُشكلان كلّا من جدول المقالات والمُستخدمين للتّعامل مع البيانات فيها، وللوصول إلى كلّ من الكائن db والصّنفين Post و User يجب عليك أن تقوم باستيرادها: >>> from project import db >>> from project.models import Post, User إن حصلت على خطأ كما يلي: Traceback (most recent call last): File "<stdin>", line 1, in <module> ImportError: No module named project فهذا يعني بأنّك لست في مجلّد kalima لذا تأكّد من أنّك اتّبعت الإرشادات السّابقة على نحو صحيح. إدخال البيانات إلى قاعدة البيانات الآن، بعد استيراد كلّ من الكائن db والصّنفين Post وUser نستطيع القيام بأول عمليّة، ألا وهي عمليّة الإضافة، وسنُضيف أولا مُستخدما باسم khalid وبعدها سنُضيف مقالا له بعنوان Post 1. إضافة المُستخدم لإضافة مُستخدم سنُنشئ أولا كائنا من الصّنف User مع المعاملات التّي حدّدناها سابقا في دالّة init الخاصّة بالصّنف، ما يعني بأنّنا سنُنشئ مُستخدما كما يلي: user = User('khalid', 'email@example.com', 'password') وهنا يكون التّرتيب مُهمّا ومُطابقا لترتيب المُعاملات في الدّالة __init__، ويُمكنك كذلك تسميّة كلّ معامل وتغيير التّرتيب إن لم تتذكّر سوى أسماء المعاملات كما يلي: user = User(password='password', email='email@example.com',name='khalid') كما تُلاحظ عندما تذكر اسم كلّ معامل، فالتّرتيب غير مهم خلافا للمثال الأول. بعد إنشاء الكائن من صنف المُستخدم بالبيانات التّي نُريدها، يُمكننا الوصول إلى كل قيمة أو تغييرها ببساطة: >>> user = User('khalid', 'email@example.com', 'password') >>> user.name 'khalid' >>> user.email 'email@example.com' >>> user.password 'password' >>> user.email = 'khalid@example.com' >>> user.email 'khalid@example.com' >>> user <username: khalid | email: khalid@example.com > كما تُلاحظ، يُمكننا الوصول إلى كل قيمة من القيم التّي حدّدناها سابقا وكذا تعديلها بإسناد قيمة جديدة بكل بساطة. لاحظ كذلك مُخرجات آخر أمر، يُمكنك أن ترى بأنّ طريقة التّنسيق هذه هي نفسها ما قد حدّدناه سابقا في الدّالة __repr__ بالصّنف User، وإليك تذكيرا سريعا: def __repr__(self): return '<username: {} | email: {} >'.format(self.name, self.email) وأنبّه إلى أنّنا لم نغيّر أي شيء بعد في قاعدة البيانات، وإنشاء الكائن ليس سوى تمهيد لإضافته إلى جلسة تتواجد في الكائن db ثمّ تأكيد التّغييرات لإضافة المُستخدم إلى قاعدة البيانات حقيقة، ولإضافة الكائن user الذي أنشأناها قبل قليل إلى الجلسة نقوم بما يلي: >>> db.session.add(user) والجلسة هنا عبارة عن مكان لإضافة البيانات لوضعها بعد ذلك في قاعدة البيانات، وعند إضافة كل كائن إلى الجلسة لا يُضاف إلى قاعدة البيانات حقيقة إلى أن تنفّذ الأمر db.session.commit. لذا لإضافة المُستخدم خالد إلى قاعدة البيانات PostgreSQL فسننفّذ الأمر التّالي: >>> db.session.commit() بعد تنفيذ الأمر والعودة إلى أداة psql لعرض جميع البيانات في جدول المُستخدمين فستلاحظ بأنّ المُستخدم الذي أضفناه موجود بالفعل: kalima=# select * from users; id | name | email | password | created_date ----+--------+--------------------+----------+---------------------------- 1 | khalid | khalid@example.com | password | 2017-04-08 18:48:05.316805 (1 row) لاحظ بأنّ العمود id أخذ القيمة 1 رغم أنّنا لم نُحدّدها، ولو أضفنا مُستخدما آخر الآن لكان رقم مُعرّفه 2 ولو حذفت مثلا المُستخدم الذي يحمل رقم المعرّف 1 وأضفت مُستخدما آخر، فرقم مُعرّف هذا المُستخدم لن يأخذ مكان المُستخدم الأول بل سيأخذ رقما جديدا مُخالفا للأرقام السّابقة؛ ولاحظ كذلك بأنّ تاريخ الإضافة قد أضيف دون أن نقوم بتحديده يدويا. إضافة المقال بعد أن قُمنا بإضافة المُستخدم، حان الوقت لإضافة مقال خاص به، بالطّبع في التّطبيق المُستخدِم هو من سيُضيف مقالاته، وبعد تسجيله الدّخول، سنستطيع إضافة المقال ووضع قيمة مفتاح مُعرّفه كقيمة للمُعامل author_id، وسنقوم في هذا الجزء بإعطاء مثال لكيفيّة إضافة مقال وإسناد كاتب له، وفي الجزء التّالي سوف نضيف المزيد من المقالات والمُستخدمين لشرح كيفيّة الوصول إلى بيانات كل مُستخدم بما فيها مقالاته، وكذا كاتب كلّ مقال. لإضافة المقال ذي العنوان Post 1 نقوم بنفس الشّيء، أولا نُنشئ كائنا من الصّنف Post ثمّ نُسند له القيم التّي حدّدناها سابقا في الدّالة __init__ الخّاصّة بالصّنف، لكن هذه المرّة نقوم بإضافة مُعامل آخر، وهو عبارة عن قيمة رقم مُعرّف المُستخدم الذي أضاف المقال، وبما أنّنا نمتلك مُستخدما رقم مُعرّفه 1 فهذا يعني بأنّنا نستطيع أن نُسند له هذا المقال بصفته كاتبا له. >>> post = Post('Post 1', 'post 1 content', author_id=1) >>> post <title Post 1 | post 1 content > >>> db.session.add(post) >>> db.session.commit() >>> post.author <username: khalid | email: khalid@example.com > >>> post.author.name u'khalid' >>> post.author.email u'khalid@example.com' >>> post.author.password u'password' كما تُلاحظ، يُمكنك تحديد أسماء معاملات إن أردت، وترك أسماء أخرى على شرط أن تكون القيم مُرتّبة، فقد ذكرت اسم المُعامل author_id فقط لكي تفهم القيمة الثّالثة، وسيتوجّب عليك ذكر الاسم إن كان لك العديد من المفاتيح الأجنبيّة (أرقام مُعرّف تُشير إلى جداول كثيرة) لأنّها ستكون كلّها عبارة عن أرقام لا معنى لها لمن يقرأ الشّيفرة، ويجب عليك أن تتذكّر ترتيبها باستمرار، لذا من المُفضّل تجنّب هذا بذكر اسم كلّ معامل. لاحظ كذلك عندما نستدعي الكائن، فإنّ الدّالة __repr__ تستبدل كلّا من المنطقتين بالعنوان والمحتوى كما هو مُتوقّع. بعد إضافة المقال إلى الجلسة ثمّ تأكيد التّغييرات وكتابتها إلى قاعدة البيانات، أصبح بإمكاننا الآن الوصول إلى كاتب المقال عن طريق post.author والمُخرج عبارة عن كائن يحمل بيانات المُستخدم الذي أدخل البيانات كما لو أنّك حصلت عليه من قاعدة البيانات، إذ تستطيع الوصول إلى قيمة أي عمود في جدول المُستخدمين والنّتيجة تكون عبارة عن سلسلة نصيّة مسبوقة بحرف u للإشارة إلى أنّها عبارة عن Unicode وبالطّبع يُمكنك التّعامل معها كما تتعامل مع سلسلة نصيّة عاديّة. بعد الوصول إلى المُستخدم كاتب المقال، يُمكنك الحصول على مقالاته الأخرى كذلك: >>> post.author.posts.all() [<title Post 1 | post 1 content >] المُخرج عبارة عن قائمة من المقالات يُمكنك الدّوران حولها باستخدام حلقة for ولأنّنا لا نمتلك سوى مقال واحد حاليّا، سنقوم بالوصول إليه مُباشرة برقم العنصر في القائمة: >>> post.author.posts[0] <title Post 1 | post 1 content > >>> post.author.posts[0].title u'Post 1' >>> post.author.posts[0].content u'post 1 content' >>> post.author.posts[0].author <username: khalid | email: khalid@example.com > لاحظ أنّنا استطعنا الوصول إلى كاتب المقال مُجدّدا في آخر سطر، يُمكنك الاستمرار إلى ما لانهاية، المقال –> كاتبُه –> مقال كَتَبه –> كاتبُه –> مقال كَتَبه –> كاتبُه وهكذا… ما يعني بأنّ السّطر التّالي مُمكن: >>> post.author.posts[0].author.posts[0].author.posts[0].author.posts[0].author.posts[0].author.name u'khalid' بالطّبع، لا فائدة من استعمال SQLAlchemy بهذه الطّريقة، إنّما يجب عليك أن تفهم كيف يعمل، وبأنّ مثل هذه الأمور مُمكنة فربّما تُفيدك في تفادي بعض الأخطاء البرمجيّة. خاتمة تعرّفنا في هذا الدّرس على كيفيّة استعمال مُفسّر لغة بايثون للتّعامل مع قاعدة البيانات الخاصّة بنا، وتمكّنّا من إضافة سجلّ إلى جدول المُستخدمين وسجلّ آخر إلى جدول المقالات. في الدّرس القادم، سنطّلع على كيفيّة إنشاء العديد من السّجلات لنتمكّن من العمل معها في بقيّة الدّروس الخاصّة بالتّعامل مع قاعدة البيانات باستعمال مكتبة SQLAlchemy لتتمكّن من تطوير تطبيقات ويب أفضل وأكثر تعقيدا باستعمال إطار العمل Flask.
  2. مقدّمة بعد أن تعرّفنا على كيفيّة تجهيز جدولي المقالات والمُستخدمين اللّذين سيُشكّلان أساس بيانات تطبيقنا، وبعد أن تعرّفنا في الدّرس السّابق على كيفيّة الرّبط بين الجدولين ليكون لكلّ كاتب مقالاته ولكل مقال كاتب خاص به، حان الوقت لإنشاء الجدولين في قاعدة البيانات والتّعرّف على كيفيّة استغلال كلّ من مكتبة SQLAlchemy وإضافة Flask-SQLAlchemy للتّعامل مع البيانات. لماذا الدّروس القادمة أساسيّة؟ هذا الدّرس والدّروس القادمة مُهمّة لك إن كنت ترغب بالاعتماد على قواعد بيانات SQL ومكتبة SQLAlchemy في تطوير تطبيقاتك، وما ستتعلّمه من هذه الدروس سيُفيدك في حالات كثيرة، وستتمكّن من القيام بأكثر العمليّات شيوعا باستخدام SQLAlchemy عوضا عن لغة SQL ما سيبقي شفرتك نظيفة وسيُمكّنك من العمل مع بياناتك بأريحيّة أكثر. ورغم أنّنا لن نستعمل الكثير من الأشياء التّي سأذكرها في تطبيق “كلمة”، إلّا أنّ الإلمام بها مهم جدّا، وعندما أقول بأنّ هذه المفاهيم مهمّة فأنا لا أعني أن تحفظها عن ظهر قلب، إذ يُمكنك أن تعود إلى درس مُعيّن في كلّ مرّة تُريد أن تتذكّر كيفيّة القيام بعمليّة معيّنة باستخدام مكتبة SQLAlchemy وإضافة Flask-SQLAlchemy، وأشجّعك على توظيف كل ما ستتعلّمه لإضافة ميّزات جديدة إلى التّطبيق الذي سنبنيه سويّا (مثلا إضافة جزء لعرض مقال عشوائي في كلّ مرّة يُعاد فيها تحميل الصّفحة)، وإن أردت أن تعرف كيفيّة القيام بعمليّة أخرى من عمليّات SQL التّقليديّة باستخدام SQLAlchemy فعد أولا إلى التّوثيق الرّسمي للمكتبة أو ضع سؤالا مصاغا بوضوح ولغة سليمة في قسم الأسئلة والأجوبة للحصول على إجابة كافيّة. إنشاء جدولي المقالات والمُستخدمين في قاعدة البيانات بعد أن أنشأنا الصّنفين المسؤولين عن إنشاء الجدولين في ملفّ models.py الذي يقع تحت مُجلّد project أصبح بإمكاننا إنشاء الجدولين عن طريق مكتبة SQLAlchemy اعتمادا على محتويات ملفّ models.py والكائن db الذي سبق لنا وأنشأناها بمُساعدة إضافة Flask-SQLAlchemy. بعد إنشاء الجدولين، ستُطبّق التّغييرات مُباشرة إلى خادوم PostgreSQL الذي نُصّب بجهازك المحلي، ما يعني بأنّك ستتمكّن من التّعامل مع قاعدة البيانات باستخدام لغة SQL والميّزات التي تُوفّرها قاعدة البيانات PostgreSQL. لإنشاء الجدولين في قاعدة بياناتنا، سنقوم أولا بإنشاء ملف باسم create_db.py في المجلّد الرّئيسي kalima وسنضع به ثلاثة أسطر فقط: # create_db.py from project import db from project.models import Post, User db.create_all() تأكّد فقط من أنّ إعداد SQLALCHEMY_DATABASE_URI يُشير إلى قاعدة بيانات PostgreSQL التّي أنشأناها سابقا باسم kalima وأنّ اسم المُستخدم وكلمة المرور صحيحان في ذات الإعداد، وللمزيد أعد إلقاء نظرة على درس تجهيز قاعدة البيانات PostgreSQL ودرس إنشاء جداول البيانات. كما تُلاحظ، الشّفرة بسيطة، أولا نستدعي الكائن db من حزمة المشروع، ثمّ نستدعي كلّا من الصّنف Post وUser لإعلام SQLAlchemy بأنّنا قد حدّدنا هذه الجداول والأعمدة الأساسيّة فيها، وبعد ذلك نقوم بإنشاء الجداول في قاعدة البيانات باستعمال الدّالة db.create_all. وبنفس الطّريقة لو أردت حذف الجداول يُمكنك استدعاء الدّالة db.drop_all عوضا عن db.create_all. وإن أمكنك الوصول إلى سطر أوامر PostgreSQL عن طريق أداة psql فيُمكنك الاتّصال بقاعدة البيانات kalima وعرض الجداول فيه، بالأمرين التّاليين: postgres=# \c kalima You are now connected to database "kalima" as user "postgres". kalima=# \d No relations found. الأمر الأول للاتّصال بقاعدة البيانات الخاصّة بتطبيقنا، وذلك لأنّ الخادوم يحتوي على أكثر من قاعدة بيانات واحدة، أمّا الأمر الثّاني فهو لعرض الجداول المُتواجدة بقاعدة البيانات، وبما أنّنا لم ننفّذ ملفّ create_db.py بعد فالنّتيجة هي بطبيعة الحال أنّنا لا نمتلك أية جداول أو علاقات كما تُلاحظ من الرّسالة No relations found. الآن، نفّذ الملفّ عبر الأمر: python create_db.py إن لم تلاحظ أية رسالة، فهذا يعني بأنّ كل شيء بخير، ولو عدت إلى سطر أوامر psql وأعدت تنفيذ الأمر \d للاحظت ما يلي: List of relations Schema | Name | Type | Owner --------+--------------+----------+---------- public | posts | table | postgres public | posts_id_seq | sequence | postgres public | users | table | postgres public | users_id_seq | sequence | postgres ربّما تختلف قيمة العمود Owner لديك لأنّها تُشير إلى اسم المُستخدم الذي أنشأ الجداول أو مالكها، وهذا الاسم هو نفسه الذي تُحدّده في إعداد قاعدة البيانات الذي سبق وأن أشرنا إليه، لاحظ معي فقط كلّا من الجدول posts و الجدول users: public | posts | table | postgres public | users | table | postgres بما أنّ نوع العلاقة هو table (جدول)، فهذا يعني بأنّنا استطعنا إنشاء الجدولين بنجاح، ونستطيع الآن التّعامل معه بلغة SQL مباشرة من أداة psql. في ما يلي استعلامان يُمكنك تنفيذهما للحصول على جميع السّجلات في كلّ جدول: select * from posts; select * from users; بالطّبع، لأنّنا لم نُضف أية سجلّات بعد، فالمُخرج سيكون كما يلي في كلتا الحالتين: Posts Table: id | title | content | created_date | author_id ----+-------+---------+--------------+----------- (0 rows) Users Table: id | name | email | password | created_date ----+------+-------+----------+-------------- (0 rows) وكما تُلاحظ، جميع الأعمدة التّي سبق وأن حدّدناها في ملفّ models.py باستعمال db.Column متواجدة في هذين الجدولين. يُمكنك بالطّبع إضافة بعض السّجلّات بلغة SQL إن أردت ذلك بالاعتماد على جملة INSERT INTO وكذا التّعامل مع البيانات بمُختلف الطّرق التّي تُتيحها PostgreSQL، ولكنّنا لن نستعمل لغة SQL لأنّنا نمتلك أداة SQLAlchemy وإضافة فلاسك الخاصّة به. ختاما تعرّفنا في هذا الدّرس على كيفيّة إنشاء جدولي المقالات والمُستخدمين في قاعدة البيانات مُباشرة باستعمال الدّالة db.create_all() التّي توفّرها لنا إضافة Flask-SQLAlchemy. في الدّرس القادم، سنتعرّف على كيفيّة استعمال مُفسّر لغة بايثون للتعامل مع قاعدة البيانات بمُساعدة من مكتبة SQLAlchemy، وبذلك سنتمكّن من التواصل مع قاعدة البيانات وإدارة سجلات الجداول فيها.
  3. مُقدّمة: بعد أن عرّفنا كلّا من جدول المقالات وجدول المُستخدمين، بقيت لنا خطوة الرّبط بينهما ليُشير كلّ مقال إلى كاتبه ونستطيع الوصول إلى مقالات كلّ مُستخدم، وسنقوم بهذا الأمر باستغلال خصائص قواعد بيانات SQL العلائقيّة لإنشاء علاقة واحد للعديد، بحيث يُمكن لكلّ مُستخدم أن يمتلك العديد من المقالات ويكون لكلّ مقال كاتب واحد فقط. المُشكلة عند تصفّحك لتطبيقات الوب في وقتنا الحالي، ستُلاحظ بأنّ المُستخدمين يمتلكون بياناتهم الخاصّة، مثلا يُمكن للمُستخدم الواحد أن يمتلك عدّة مقالات وتدرج تحت اسمه، ويُمكن أن يمتلك كذلك عدّة تعليقات باسمه، إذن كيف يُمكن أن نُدرج مقالات من جدول المقالات الخاصّ بنا إلى المُستخدم الذي يقوم بإضافتها؟ بمعنى آخر، كيف يُمكن للمُستخدم الواحد أن يمتلك عدّة مقالات مُندرجة باسمه؟ الحل الخاطئ الطّريقة التّي يُمكن أن تُفكّر فيها هي بإدراج رقم مُعرّف كل مقال يُضيفه المُستخدم إلى عمود خاص به مع الفصل بينها بمسافة أو شيء من هذا القبيل، بعدها تحصل عليها وتقوم باستخراج مقالات كل مُستخدم. الجدول سيكون كما يلي: id | name | posts 1 | Ali | 1, 3, 5 ... أهملت هنا عمودي البريد الإلكتروني وكلمة المرور لأنّها غير مُهمّة في مثالنا. الآن إن نظرت إلى العمود posts فستجد بأنّ المقالات ذات المُعرّفات 1 و 3 و 5 قد أضافها المُستخدم Ali. هذه الطّريقة فكرة سيّئة جدّا، فلو أردنا حذف مقال فحذف علاقته بالمُستخدم أمر مُعقّد، وهناك العديد من المشاكل مع استخدام هذه الطّريقة، وقد أشرت إليها لأنّها شائعة جدّا ويجب أن تحذفها من ذهنك قبل أن تحدث أمور أسوأ، فإن أردت أن تكون مُطوّر وب جيّدا فعليك تعلّم الطّرق الصّحيحة وعليك كذلك الإلمام بالطّرق الخاطئة في التّطوير. الطّريقة الصّحيحة لربط سجلّ واحد بالعديد من السّجلّات في جدولين مُختلفين علاقة الواحد-للعديد أو One-to-Many Relationship هي طريقة لربط جدولين أو أكثر بحيث يمتلك كلّ فرد في جدول العديد من السّجلّات في جدول آخر. في مثالنا، سيمتلك المُستخدم الواحد عدّة مقالات باسمه. للقيام بهذا الأمر، نقوم بوضع عمود آخر في الجدول الذي سيمتلك العديد من السّجلّات الخاصّة بفرد واحد من جدول آخر ليحمل قيمة رقم مُعرّف هذا الفرد، أي أننا سنُضيف عمودا آخر في جدول المقالات ليحمل رقم مُعرّف المُستخدم الذي أضاف المقال ليكون جدول المقالات كما يلي: | id | title | user_id | 1 | Post 1 | 1 | 2 | Post 2 | 2 | 3 | Post 3 | 1 | 4 | Post 4 | 3 لنفترض بأنّ لدينا ثلاثة مُستخدمين في جدول المُستخدمين كما يلي: | id | name | 1 | Ali | 2 | Abdelhadi | 3 | Hassan مُجدّدا، قمت بإهمال الأعمدة الأخرى لأنّها لا تهمّنا في هذا المثال، وكما تُلاحظ لكل مقال قيمة في عمود user_id لتُشير إلى المُستخدم الذي أنشأ هذا المقال، وعند النّظر إلى جدولي المقالات والمُستخدمين والسّجلّات المتواجدة فيها حاليّا، يتبيّن لنا بأنّ المستخدم Ali قد أضاف مقالين، المقال ذو رقم المُعرّف 1 و المقال ذو رقم المُعرّف 3، أمّا Abdelhadi فهو صاحب المقال ذي المُعرّف 2 أمّا المقال الأخير فمن الواضح بأنّ المُستخدم Hassan هو من أضافه. هذه هي علاقة الواحد للعديد ببساطة، عندما يمتلك سجلّ واحد من جدول العديد من السّجلّات في جدول آخر، فإنّنا نُضيف عمودا إلى الجدول الذي يحمل العديد من السّجلات ليحمل كل سجلّ منه قيمة المفتاح الأولي Primary Key للسّجل الواحد في الجدول الآخر والذي يُسمّى فيه مفتاحا أجنبيا Foreign Key، أي أنّ العمود user_id عبارة عن مفتاح أجنبي لأنّه يحمل قيمة المفتاح الأولي الخاصّ بالمُستخدم، وعندما نقوم بتحديد عمود على أنّه مفتاح أجنبي، فالعلاقة بين الجدولين تكون مفهومة بالنّسبة لقاعدة البيانات لدينا، كما يُساعدنا ذلك على تخطّي بعض المشاكل في حالة ما أضيفت قيمة خاطئة إلى العمود عوضا عن قيمة صحيحة. في SQLAlchemy يُمكننا إنشاء علاقة واحد للعديد بسهولة وبساطة بإضافة سطرين فقط إلى الشفرة التّي حدّدناها سابقا، سطر في صنف (جدول) المقالات لإضافة عمود باسم author_id والذي سيكون عبارة عن مفتاح أجنبي يُشير إلى قيمة المفتاح الأولي للمُستخدم، وسطر في صنف المُستخدم لنتمكّن من الوصول إلى مقالات كل مُستخدم ببساطة. أضف هذا السّطر في آخر الصّنف Post: # project/models.py author_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) لتعريف مفتاح أجنبي، نستعمل الدّالة db.ForeignKey مع تمرير اسم الجدول واسم المفتاح الأولي مفصولين بنُقطة (في هذا المثال users.id) ونقوم كذلك بإضافة المُعامل nullable وإسناد القيمة المنطقيّة False له للتأكّد من أنّ الحقل الذي يربط بين المُستخدم والمقال الذي أضافه لا يحمل قيمة فارغة. بعد إضافة المفتاح الأجنبي، حان الوقت لإخبار SQLAlchemy بأنّنا نريد الحصول على مقالات كلّ مُستخدم، والمُستخدم الذي أضاف كلّ مقال انطلاقا من العلاقة بين الجدولين، لذا أضف السّطر التّالي في آخر الصّنف User: # project/models.py posts = db.relationship("Post", backref="author", lazy="dynamic") في هذا السّطر، نقوم بتعريف العلاقة بين المُستخدم والمقال عن طريق الدّالّة db.relationship مع تمرير ثلاثة مُعاملات، الأول عبارة عن اسم الصّنف الذي يشير لجدول المقالات، والمُعامل backref يُمكّننا من الوصول إلى المُستخدم الذي كتب المقال عن طريق الاسم author أمّا المعامل الأخير فهو لتحديد نوعيّة النّتيجة التّي سنحصل عليها عند طلب جميع المقالات التّي كتبها أحد المُستخدمين، وقد وضعنا القيمة dynamic لتكون النّتيجة عبارة عن استعلام ديناميكي لجميع المقالات التّي كتبها المُستخدم، وهكذا سنحصل على مرونة أكثر عند طلب المقالات وسنتمكّن من التّعامل معها على أنّها كائنات يُمكننا استغلالها بجميع ما توفّره مكتبة SQLAlchemy من طرق ودوال مُساعدة، وسنرى تطبيقا لهذا الكلام فيما بعد. سنُضيف كذلك عمود author_id إلى التّابع __init__ الخاصّة بالصّنف Post وبالتّالي سيُصبح التّابع كما يلي: def __init__(self, title, content, author_id, created_date = None): self.title = title self.content = content self.author_id = author_id self.created_date = datetime.utcnow() بعد تحديد العلاقة بين الجدولين، سنستطيع إضافة مقال وإسناد رقم مُعرّف المُستخدم أو المُستخدم ككاتب للمقال الذي أضافه لنتمكّن فيما بعد من الحصول على كاتب كل مقال بجميع معلوماته (بريده الإلكتروني، اسمه …)، وكذا المقالات التّي كتبها كلّ مُستخدم مع مرونة في التّعامل معها عبر ما تُوفّره لنا مكتبة SQLAlchemy من مُساعدة. خاتمة بعد أن تعرّفنا على طريقة ربط المُستخدم ومقالاته والمقالات وكاتبها، أصبحنا نستطيع تطبيق التّغييرات إلى قاعدة بياناتنا والتّعامل مع السّجلّات بعمليّات قواعد البيانات المعروفة اختصارا بـCRUD أو Create, Read, Update, Delete، أي الإضافة/الإنشاء، القراءة/العرض، التّعديل والحذف.
  4. مُقدّمة بعد تجهيز قاعدة بيانات PostgreSQL التي سنستعملها في تطبيقنا، وبعد أن تعرّفنا في الدرس السابق على كيفيّة تنصيب وتجهيز كل من مكتبة SQLAlchemy و إضافة Flask-SQLAlchemy، حان الوقت لتجهيز جدولين من أهم جداول تطبيقات إدارة المحتوى، ألا وهي جدول المقالات الذي يمثل المحتوى بعينه وجدول المُستخدمين الذين سيُدِيرُون المحتوى، وبالتالي فسنقوم في هذا الدرس بوضع أساس لتطبيق “إدارة المحتوى” عبر تجهيز الطرف الذي سيقوم بالإدارة والطرف الذي يُمثل المحتوى. تذكير نعلم بأنّ قاعدة البيانات تحتوي على عدة جداول، كل جدول يمثل نوعا من المحتوى، وكل جدول يشمل عدة أعمدة، فمثلا جدول المُستخدمين سيحتوي على عمود لأسماء المستخدمين، وعمود لكلمات المرور، بحيث تكون قيمة كل عمود مربطة بمستخدم واحد فقط. تعرّفنا في الدرس السابق على الشكل العام لجداول قاعدة البيانات عند استعمال إضافة Flask-SQLAlchemy وقلنا بأنّ جدولا باسم table_name سيكون كالتّالي: from project import db class TableName(db.Model): __tablename__ = 'table_name' column_name = db.Column(db.Type, args) الجدول به عمود واحد باسم column_name ونقوم بإخبار SQLAlchemy بأنّ هذا عمود في الجدول عبر استخدام التّابع db.Column الذي يقبل عدّة معاملات لتحديد نوعية العمود وما يمكن له أن يقبل من قيم. نقوم بتحديد نوع البيانات التي يجب أن يقبلها العمود عبر استخدام المعامل db.Type مع استبدال Type بأحد الأنواع التي يدعمها SQLAlchemy. ما يلي أكثر أنواع القيم استخداما والتي يدعمها SQLAlchemy: Integer الأعداد الصّحيحة String(size) سلسلة نصيّة، size مُعامل يشير إلى أقصى قيمة لعدد المحارف في السّلسلة النّصيّة، مثلاString(25) يشير إلى أنّ العمود يُمكن أن يحمل سلسلة نصيّة بحدود خمسة وعشرين محرفا فقط ولا يُمكن تجاوز هذا الحد. Text نوع للإشارة إلى أنّ هذا العمود يُمكن أن يحتوي على النّصوص الطّويلة. DateTime نوع يُشير إلى أنّ العمود سيحمل تواريخ مُعبّر عنها بالكائن datetime في لغة بايثون، وسنستخدم هذا النّوع لوضع تاريخ نشر للمقال وتاريخ لانضمام مُستخدم معيّن. يُساعد تحديد نوع القيم في العمود على تجنّب الكثير من الأخطاء، ومن المُفضّل أن تختار دائما النّوع الذي يُناسبك أكثر لتحفظ المساحة وتتجنّب بعض الهجمات المُمكنة، فمثلا لو كان نوع القيمة Text في حقل اسم المُستخدم لتمكّن أحدهم من وضع نص كاسم مُستخدم له، وإن عرضناه في صفحة لكانت تلك الصّفحة بشعة لوجود نص عوضا عن كلمة أو كلمتين، هذا بالإضافة إلى أنّ المساحة في الخادوم ستُستهلك بشكل غير معقول. أعلم بأنّ الكلام النّظري مُمل بعض الشّيء، لكنّه مهم جدّا لتفهم ما سنقوم به تاليّا، وسنرى تطبيقا لهذا الكلام عند إنشاء جدولي المقالات والمُستخدمين في ما بعد وأتمنّى أن تُساعد هذه المُقدّمة على تقليص الصّداع الذي يحدث عندما تتعلّم شيئا لأول مرّة. تجهيز جدول المقالات بعد أن وضّحنا المفاهيم الأساسيّة، حان الوقت لتطبيق هذه المفاهيم وإنشاء الجداول التّي سنتعامل معها في تطبيقنا، تذكر بأنّ هذه الجداول عبارة عن أصناف بايثون عادية، ويجب عليك وضعها داخل ملفّ models.py المتواجد بمُجلّد project. سنبدأ بجدول المقالات الذي سيكون كالآتي: # project/models.py from datetime import datetime from project import db class Post(db.Model): __tablename__ = "posts" id = db.Column(db.Integer, primary_key=True) title = db.Column(db.String(255), nullable=False) content = db.Column(db.Text, nullable=False) created_date = db.Column(db.DateTime) def __init__(self, title, content, created_date = None): self.title = title self.content = content self.created_date = datetime.utcnow() def __repr__(self): return '<title {} | {} >'.format(self.title, self.content) مُلاحظة: تذكّر بأنّ جميع الجداول التّي نعرّفها عبارة عن أصناف في ملفّ project/models.py. أولا، نقوم باستيراد الكائن datetime لنستعمله في تأريخ إضافة المقال، بعدها نستدعي الكائن db الذي عرّفناه سابقا، بعد ذلك نقوم بتطبيق مفهوم الصّنف كجدول لإنشاء جدول المقالات باسم Post الذي يرث من db.Model، بعدها نقوم بتحديد اسم الجدول posts ثمّ نعرّف عمود رقم المُعرّف كمفتاح أولي، ثمّ العنوان كسلسلة نصيّة بحدود 255 حرفا، محتوى المقال كنصّ، تاريخ إضافة المقال من نوع db.DateTime. بعد تعريف جميع الأعمدة، نعرّف الأعمدة التّي يُمكننا إدارتها عبر الدّالة __init__، وتشمل هذه الأعمدة حاليا العنوان والمحتوى وتاريخ الإضافة فقط، وإدارة تاريخ الإضافة اختياري لأنّنا سبق وأن وضعنا له قيمة افتراضيّة عبارة عن تاريخ إضافة السّجل إلى قاعدة البيانات والذي نصل إليه من الدّالة datetime.utcnow، الآن إن أردنا إضافة قيم إلى الجدول فسنحتاج إلى توفير عنوان المقال ومحتواه فقط، أمّا العمودان الآخران فيهتمّ بهما SQLAlchemy. بعد الانتهاء من التّابع الخاص __init__ نعرّف التّابع الخاص __repr__ ليُساعدنا على عرض عنوان المقال ومحتواه بعد استعلامه بـSQLAlchemy ككائن، وقد سبق وأن شرحت وظيفة هذه الدّالة الخاصّة في درس البرمجة كائنيّة التوجّه ضمن سلسلة تعلّم بايثون، وسنرى في ما بعد نتيجة هذه الدّالة وبماذا ستُفيدنا. تجهيز جدول المُستخدمين سنقوم بإنشاء جدول المُستخدمين بنفس الطريقة، صنف باسم User، جدول باسم users، رقم مُعرّف كقيمة أوليّة، اسم مُستخدم، بريد إلكتروني، كلمة المرور، وتاريخ الانضمام. class User(db.Model): __tablename__ = "users" id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(25), nullable=False) email = db.Column(db.String(25), nullable=False) password = db.Column(db.String(25), nullable=False) created_date = db.Column(db.DateTime) def __init__(self, name, email, password, created_date = None): self.name = name self.email = email self.password = password self.created_date = datetime.utcnow() def __repr__(self): return '<username: {} | email: {} >'.format(self.name, self.email) هذه المرّة سيتوجّب علينا تحديد كلّ من اسم المُستخدم، كلمة المرور وبريده الإلكتروني إذا أردنا إضافة سجل مُستخدم إلى قاعدة البيانات، أمّا الدّالّة __repr__ فستعرض كلّا من اسم المُستخدم وبريده الإلكتروني. مُلاحظة: حاليّا نقوم بتخزين كلمات المرور على شكل نصوص واضحة، ويُمكن لأي شخص أن يحصل عليها ببساطة إن استطاع الوصول إلى قاعدة بيانات التّطبيق، وهذا أمر خطير جدّا، إذ أنّ حماية بيانات مُستخدمي التّطبيق أمر مهم جدّا، ولابد من تشفير كلمات المرور قبل إدخالها إلى قاعدة البيانات، وسنقوم بذلك فيما بعد، فالهدف الرّئيسي من هذا الدّرس هو إعطاؤك مقدّمة لكيفيّة تجهيز الجداول في قاعدة البيانات أمّا الحماية فسنتطرّق إليها لاحقا، لذا لا تحاول القيام بتخزين كلمات المرور بهذه الطّريقة، وتابع السّلسلة إلى نهايتها لتتعلّم كيفيّة تطوير تطبيقاتك بإطار العمل Flask بشكل آمن. خاتمة بعد أن تعرّفنا على كيفيّة تجهيز جداول قاعدة البيانات التّي سنتعامل معها في تطبيقنا باستعمال مكتبة SQLAlchemy وإضافة Flask-SQLAlchemy وتعرّفنا على بعض المفاهيم المُهمّة في التّعامل مع قواعد البيانات بهذه المكتبة، بقيت نقطة مهمّة يجب الإشارة إليها قبل إنشاء الجداول في قاعدة البيانات وجلب، إضافة وتعديل وحذف السّجلات منها، ألا وهي علاقة الواحد للعديد One-to-Many Relationship، وهي العلاقة التّي سنستكشفها وسنستعملها لربط كلّ مقال بالمُستخدم الذي أضافه وربط المقالات بكاتبها في الدّرس القادم.
  5. flask_cms

    مُقدّمة لا شك بأنّ معظم تطبيقات اليوم تعتمد على نوع من أنواع البيانات، لذا لا بدّ من حفظها في مكان مُناسب، وتعتبر قواعد البيانات التّي يُمكن التّعامل معها بلغة 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 مكتوب بلغة بايثون. في الدرس القادم، سنقوم بإنشاء جدولي المقالات والمُستخدمين في قاعدة بياناتنا.
  6. تجهيز قاعدة البيانات بما أنّنا سنستخدم قاعدة البيانات 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 وهذا بالضّبط ما سنقوم به في الدّرس القادم.
  7. flask_cms

    مُقدّمة تعرّفنا سابقا على حلقة التّكرار 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.
  8. flask_cms

    مُقدّمة تعرّفنا في الدّروس السّابقة على العديد من ميّزات مُحرّك القوالب 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.
  9. flask_cms

    مُقدّمة تعرّفنا في الدّرس السّابق على التعليمة 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 لتفادي تكرار الشّيفرة والحصول على تجربة تطوير أفضل.
  10. مُقدّمة تكرار الشّيفرة من أسوء المُمارسات التّي يُمكن لأي مُطوّر القيام بها، إذ تستهلك الكثير من الوقت الثّمين الّذي يُمكن صرفه في تطوير ميّزات أخرى وأمور أكثر أهميّة؛ سنتعرّف في هذا الدّرس على كيفيّة استغلال مُحرّك القوالب 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 وكيفيّة الاستفادة منه.
  11. تمهيد إلى الآن، تعرّفنا على العديد من أساسيّات تطوير الويب باستعمال كلّ من إطار العمل 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، في الدّرس القادم سنتعرّف على جزء آخر لا يقلّ أهميّة عمّا درسناه في الدّروس السّابقة، ألا وهو كيفيّة التّعامل مع أكثر من ملفّ عبر تضمينها بعضها ببعض لتفادي تكرار الشّيفرة البرمجيّة.
  12. مُقدّمة بعد أن تعرّفنا على المُرشّحات والاختبارات في مُحرّك القوالب 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.
  13. مُقدّمة بعد أن تعرّفنا على ماهية ميّزة الاختبارات في مُحرّك القوالب 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 أفضل.
  14. مُقدّمة بعد التّعرف في الدّرس السّابق على مفهوم الاختبارات (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، سننتقل في الدّرس القادم إلى كيفيّة إنشاء اختبارات خاصّة بنا لاستعمالها في حالة لم تجد اختبارا يُلبي حاجتك في قائمة الاختبارات المبنيّة مُسبقا.
  15. مقدّمة بعد أن ألقينا نظرة على كيفيّة استخدام إطار العمل 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 ومن ثمّ نشرها، سيتمكّن كلّ مُستخدم من الحصول على رابط خاصّ بمقالاته لنشرها للآخرين، سيتمكّن الكاتب من إضافة وسوم لمقالاته، وكذلك إدراجها ضمن قسم معيّن، لكل مقالة قسم للتّعليقات ليتمكّن المُستخدمون الآخرون من التّعليق على مقال معيّن. سيكون للتّطبيق نكهة اجتماعيّة، بحيث سيتمكن المُستخدمون من مُتابعة بعضهم البعض، وكذلك إضافة مقالات إلى المُفضّلة والإعجاب بها لنتمكّن من فرزها وترتيبها حسب الأكثر إعجابًا، الأكثر نشاطًا (أكبر عدد من التعليقات) وبعدها ستتمكّن من إضافة خاصيّات أخرى بنفسك باتّباع نفس الطّريقة، مثلًا بعد الانتهاء من التّطبيق ستتمكّن من تطبيق فكرة ترتيب المقالات حسب الأكثر إعجابًا لإضافة خاصيّات أخرى (الأكثر شعبيّة حسب عدد التّعليقات، الأكثر زيارة...). صحيح بأنّنا سنبني تطبيقًا مُعقّدا وكبيرًا، إلّا أنّني لن أشرح كلّ شيء بتفصيل ممل، سأشرح بالتّفصيل فقط الأفكار الجديدة وما تراه لأوّل مرّة حتى لا تكون هذه السّلسلة أطول من اللازم، خاصّة وأنّ بعض الأساليب ستكون مُشابهة جدّا لبعضها البعض، فمثلاً طريقة السّماح للمُستخدم بإضافة مقال خاص به مُشابهة جدّا لطريقة فرز المقالات حسب قسم مُعيّن (لكل مُستخدم مقالاته ولكلّ قسم مقالاته كذلك). سنضع للتّطبيق بعض الميّزات المُفيدة كنظام أرشفة لتأريخ كلّ مقال حسب تاريخ إضافته، كما سيكون لتعليقات المقال نظام مُشابه، وسنستغل نفس الطّريقة لتخصيص تاريخ انضمام للمُستخدم. في النّهاية، تذكّر بأنّ هذه السّلسلة من الدّروس ستكون تطبيقيّة بالدّرجة الأولى، وستتعلّم فيها كيفيّة بدء مشاريعك التّطويريّة الخاصّة بك بلغة بايثون وإطار فلاسك، أمّا كيفيّة الاستفادة القصوى منها فمعروفة، عليك تطبيق ما تعلّمته وتختبر مع إضافة ميّزات ولو لم أذكرها، وإن أردت معرفة معمّقة عن أي شيء تحدّثت عنه دون أن أفصّل، يُمكنك دائما العودة إلى التّوثيق الرّسمي للأدوات والمكتبات والإضافات المعنيّة بالأمر، وإن كان لك سؤال معيّن فلا تتردّد في طرحه على قسم الأسئلة والأجوبة على الأكاديميّة. هناك المزيد الدّروس التّي ستندرج تحت هذه السّلسلة لن تُغطي فقط ما قرَأتَهُ للتّو، بل أكثر من ذلك، فهدفي من كتابة سلسلة الدّروس هذه هو تغطيّة الفراغ الموجود في الويب العربي، إذ أنّ التّوثيقات الرّسميّة للمكتبات وأطر العمل المُستعملة حاليّا عادة ما تكون مكتوبة باللغة الانجليزيّة فقط، لذا بتوفير سلسلة دروس كهذه ستمتلك أكبر قدر مُمكن من المُساعدة لتتمكّن من العمل على تطبيقاتك الخاصّة دون أن تضطر إلى اتّباع سلسلة دروس أخرى أو الاطّلاع على التّوثيقات الرّسميّة التّي عادة ما تكون مُفيدة أكثر إذا ما أردت أن تُنجز شيئًا بسيطًا، وعلى العكس من ذلك، فكتابة تطبيق كامل (كما في هذه السّلسلة) طريقة أفضل للتّعلم. ختاما هذا المقال عبارة عن مُقدّمة بسيطة لما سنخوضه في قادم الدّروس، في الدّرس القادم، ستبدأ المُتعة مع استكشاف كيفيّة التّعامل مع ملفّات ومُجلّدات التّطبيقات الكبيرة والمُعقّدة بطريقة بسيطة.
  16. مقدّمة بعد أن تعرّفنا على بنية التّطبيق الذي سنبنيه سويا، سنبدأ بتهيئة وتنصيب بعض الحزم التّي سنعتمد عليها، وسنرى كيف يُمكننا تنظيم الملفّات لمرونة أكثر في التّعامل مع المشاريع الكبيرة، فعوضا عن وضع الشيفرة كاملة في ملفّ أو ملفّين، سنقوم باستعمال مبدأ المُخطّطات 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، الملفّ الأول مسؤول عن تشغيل الخادوم/ أمّا الملفّ الثّاني فمسؤول عن إعدادات التّطبيق. خاتمة تعرّفنا في هذا الدّرس على بنية التّطبيق الذي سنبنيه وكيفيّة توزيع ملفّاته إذ أنشأنا المُجلّدات والملفّات التّي ستكون مسؤولة عن الجانب العملي للتّطبيق، في الدّرس القادم، سنبدأ بتجهيز البيئة البرمجيّة وتنصيب أهم الحزم التّي سنحتاج إليها للبدء بتطوير التّطبيق.
  17. flask_cms

    مُقدّمة بعد أن تعرّفنا على بنية التّطبيق الذي سنبنيه سويًّا باستخدام إطار العمل 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 وكيفيّة الرّبط بين مُختلف موجّهات التّطبيق.
  18. مُقدّمة: بعد أن قمنا بتنظيم ملفّات التّطبيق على حسب الوظيفة باستعمال خاصيّة المُخطّطات، أصبح لدينا اختلاف بسيط في كيفيّة التّعامل مع ملفّات العرض التّي تحتوي على شيفرات 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 سيُخوّلك لتعلّم أساسيّات أطر عمل أخرى ومُختلف الأدوات التّي تعتمد عليه بشكل أسرع.
  19. مُقدّمة تعلّمنا في الدّروس السّابقة كيفيّة إدارة مشروع مكتوب بإطار العمل فلاسك، وقد تعرّفنا في الدّرس السّابق على كيفيّة التّعامل مع قوالب 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 والتّي يُمكنك استعمالها مُباشرة مع تطبيقات فلاسك الخاصّة بك.
  20. مُقدّمة بعد أن تعرّفنا على كيفيّة استخدام المُرشّحات في قوالب 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 كثيرة ولا يُمكن أن نذكرها جميعها في درس واحد، لذا فسنُكمل تغطيّة بقيّة المُرشّحات المُهمّة في دروس مُقبلة لنمرّ بعدها إلى جانب آخر من تطوير الويب أثناء تطويرنا لتطبيق كبير في هذه السّلسلة، لذا ترقّب بقيّة الدّروس.
  21. مُقدّمة بعد أن تعرّفنا على جزء من أهمّ المُرشّحات التّي يُمكنك استعمالها لتفادي تكرار شيفرة عدّة مرّات والحصول على تجربة تطوير أحسن، سنُكمل في هذا الدّرس ما بدأناه بالتّعرفّ على قائمة مُرشّحات أخرى لتكون لديك فكرة أفضل عن كيفيّة استخدام مُحرّك القوالب 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 المزيد من المُرشّحات المُفيدة والتّي سنتعرّف عليها في الدّرس المُقبل.
  22. مُقدّمة تعرّفنا في الدّرس السّابق على بعض من مُرشّحات مُحرّك القوالب 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.
  23. مُقدّمة تعرّفنا في الدّروس السّابقة على كيفيّة الاستفادة من مُحرّك القوالب 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 عبر التّعرف على مبدأ الاختبارات ومن ثمّ ننتقل إلى إكمال تطوير تطبيق "كلمة” في بقيّة الدّروس.
  24. flask_cms

    مُقدّمة بعد أن تعرّفنا على ماهيّة المُرشّحات وكيفيّة استخدام أهم المُرشّحات المبنيّة مُسبقا في 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 والتّي ستُساعدك على تطوير تطبيقات ويب أفضل كما ستُفيدك في فهم وتطوير تطبيق “كلمة”.