يُعد فلاسك إطار عمل للويب مبني بلغة بايثون، ويتميز بكونه صغير الحجم وسهل المعالجة، ويوفّر أيضًا عدة أدوات وميزات من شأنها جعل إنشاء تطبيقات الويب في لغة بايثون أسهل.
عند تطوير تطبيق ويب ما، من الضروري الفصل بين ما يدعى "منطق العمل business logic" و"منطق العرض presentation logic"؛ إذ يكون منطق العمل مسؤولاً عن التعامل مع طلبات المستخدمين، مُخاطبًا قاعدة البيانات لإنشاء الاستجابة المناسبة؛ في حين يُحدّد منطق العرض كيفيّة عرض البيانات للمستخدم، وذلك غالبًا باستخدام ملفات HTML لإنشاء الهيكل الأساسي لصفحة الويب التي تستجيب لطلبات المستخدمين، مع استخدام تنسيقات CSS لتنسيق مظهر تلك المكونات المبنية باستخدام HTML.
فمثلًا في حال كان لدينا تطبيق للتواصل الاجتماعي، فيوجد عادةً حقلٌ لإدراج اسم المستخدم وحقل لإدراج كلمة المرور بحيث لا يظهران إلّا في حال كون المستخدم لم يسجل دخوله إلى التطبيق بعد، ولدى كتابة المستخدم اسم المستخدم وكلمة المرور يعمل فلاسك على تطبيق منطق العمل، إذ تُستخرج البيانات (اسم المستخدم وكلمة المرور) من الطلب ليُسجَّل دخول المستخدم إذا كانت هذه البيانات صحيحة، وإلّا ستظهر رسالة خطأ، ويجري التعامل مع كيفية ظهور رسالة الخطأ بالاعتماد على منطق العرض.
يستخدم فلاسك محرّك القوالب جينجا jinja لبناء صفحات HTML ديناميكيًا باستخدام مفاهيم بايثون المألوفة، من متغيراتٍ وحلقاتٍ تكرارية وقوائم وغيرها، وبالتالي ستُستخدم هذه القوالب على أنها جزءٌ من هذا المشروع، إذ يُعرّف القالب بأنه ملف يحتوي على مكونات ثابتة وأُخرى ديناميكية، وعندما يطلب المستخدم عنصرًا من التطبيق، مثل الصفحة الرئيسية، أو صفحة تسجيل الدخول، يُمكنّنا جينجا من الاستجابة باستخدام قالب HTML، وبذلك يصبح من الممكن استخدام عددٍ كبيرٍ من الميزات غير المتوفرة أصلًا في لغة HTML المعيارية، مثل المتغيرات والعبارة الشرطية "if" والحلقات التكرارية مثل حلقة "for"، والمُرشّحات filters ومفهوم وراثة القوالب، إذ تسمح لنا هذه الخواص بكتابة صفحات HTML سهلة الإصلاح، كما تعزل جينجا شيفرات HTML تلقائيًا بهدف منع هجمات البرمجة العابرة للمواقع Cross-Site Scripting -أو اختصارًا XSS-.
سنبني في هذا المقال تطبيق ويب صغير يعمل على تصييّر عدّة ملفات HTML ضمن المتصفح معًا، إذ سنستخدم المتغيرات لتمرير البيانات من الخادم إلى القوالب، كما سنطبّق مفهوم وراثة القوالب لتجنُّب التكرار في الشيفرات البرمجية.
وسنستخدم كذلك التعابير المنطقية في بناء شيفرات القوالب من عبارات شرطية وحلقات، وسنطبّق المُرشّحات اللازمة لتعديل النصوص، وسنعمل من خلال إطار العمل بوتستراب bootstrap على إضافة التنسيقات على التطبيق ليبدو أكثر جاذبية بصريًا.
مستلزمات العمل
قبل المتابعة في هذا المقال لا بُدّ من:
- توفُّر بيئة برمجة بايثون 3 محلية، مثبّتة على حاسوبك، وسنفترض في مقالنا أن اسم مجلد المشروع هو "flask_app".
- توفّر إطار العمل فلاسك مُثبّت ضمن البيئة البرمجبة كما هو مُبيّن في الخطوة الأولى من المقال كيفية بناء موقعك الإلكتروني الأول باستخدام إطار عمل فلاسك Flask والإصدار الثالث من لغة بايثون.
- الفهم الجيد لأساسيات فلاسك، مثل مفهوم الوجهات routes ودوال العرض view في فلاسك، ويمكنك في هذا الصدد الاطلاع على المقال كيفية بناء موقعك الإلكتروني الأول باستخدام إطار عمل فلاسك Flask والإصدار الثالث من لغة بايثون المشار إليه أعلاه لفهم مبادئ فلاسك.
- فهم أساسيات لغة HTML.
الخطوة الأولى - تصيير القوالب واستخدام المتغيرات
بعد التأكّد من تفعيل بيئة البرمجة وتثبيت فلاسك، سنبدأ ببناء التطبيق، إذ تتمثّل الخطوة الأولى بعرض رسالة ترحيب بالزوار في الصفحة الرئيسية، وسنستخدم لهذا الغرض دالة فلاسك المساعدة ()render_template
لتوفير قوالب HTML مثل استجابة ضمن الصفحة، وسنتعلّم في هذا الصدد كيفيّة تمرير المتغيرات من التطبيق إلى القوالب.
بدايةً نفتح الملف "app.py" الموجود في المجلد "flask_app" لتعديله وذلك باستخدام محرّر النصوص نانو "nano" أو أي محرر آخر تفضّله:
(env) user@localhost:$ nano app.py
ونضيف الشيفرة التالية ضمنه:
from flask import Flask, render_template app = Flask(__name__) @app.route('/') def hello(): return render_template('index.html')
نحفظ الملف ونغلقه.
استوردنا في الشيفرة السابقة الصنف Flask
الذي سنستخدمه لإنشاء نسخة من تطبيق باسم "app"، كما استوردنا الدالة ()render_template
من حزمة flask
، ثمّ عرفنا دالة العرض ()hello
(وهي في الواقع دالة بايثون اعتيادية تعيد استجابةً من النمط HTTP) باستخدام المُزخرف ()app.route
، الذي يحوّل دوال بايثون الاعتيادية إلى دوال عرض في فلاسك، تستخدم دالة العرض هذه الدالة ()render_template
لتصييّر ملف قالب HTML المُسمّى "index.html".
الآن، سننشئ القالب "index.html" ضمن المجلد الفرعي "templates" من المجلد الرئيسي "flask_app"، إذ سيبحث فلاسك عن القوالب في المجلد المسمّى "templates" تحديدًا، لذا يُعدُّ الاسم مهمًا. لإنشاء هذا المجلد، نتأكد من وجودنا ضمن المجلد "flask_app" وننفذ الأمر التالي:
(env) user@localhost:$ mkdir templates
بعد ذلك، ننشئ ملفًا باسم "index.html" ضمن مجلد القوالب "templates" لنكتب ضمنه لاحقًا الشيفرات اللازمة، ومن الجدير بالملاحظة أنّه من غير الضروري التقيُّد باسم "index.html" على عكس اسم المجلد الرئيسي، فمن الممكن تغييّر اسم الملف ليصبح مثلًا "home.html"، أو "homepage.html"، أو أي اسم آخر تفضّله:
(env) user@localhost:$ nano templates/index.html
ثمّ نضيف شيفرة HTML التالية داخل الملف "index.html":
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>FlaskApp</title> </head> <body> <h1>Hello World!</h1> <h2>Welcome to FlaskApp!</h2> </body> </html>
اخترنا في الشيفرة السابقة عنوانًا للصفحة، كما أضفنا الرسالة "!Hello World" لتظهر ضمن تنسيق عنوان من المستوى الأوّل H1
، والرسالة "!Welcome to FlasApp" لتظهر ضمن تنسيق عنوان من المستوى الثاني H2
.
نحفظ الملف ونغلقه.
ولتشغيل تطبيق الويب الذي أنشأناه، وبعد التأكّد من وجودنا ضمن المجلد "flask_app" مع تفعيل البيئة الافتراضية، لا بُدّ من إرشاد فلاسك إلى موقع التطبيق (في حالتنا الملف ذو الاسم app.py) باستخدام متغير بيئة فلاسك FLASK_APP
، ولتشغيله بوضع التطوير، نضبط متغير بيئة فلاسك Flask_ENV
على الوضع development
على النحو التالي (مع ملاحظة أنّنا نستخدم الأمر set
في بيئة ويندوز عوضًا عن الأمر export
):
(env) user@localhost:$ export FLASK_APP=app (env) user@localhost:$ export FLASK_ENV=development
بعدها سنشغل التطبيق باستخدام الأمر flask run
:
(env) user@localhost:$ flask run
بعد التأكد من كون خادم التطوير ما يزال قيد التشغيل، نذهب إلى الرابط التالي باستخدام المتصفح:
http://127.0.0.1:5000/
فتظهر العبارة "FlasApp" عنوانًا للصفحة، ويجري تصييّر الرسالتين المكتوبتين في HTML لتظهرا بالتنسيق المطلوب.
تُمرّر البيانات عادةً في تطبيقات الويب من ملفات بايثون الخاصة بالتطبيق إلى قوالب HTML، ولنوضّح كيفية فعل ذلك في تطبيقنا هذا، سنمرّر متغيرًا يحتوي على التاريخ والوقت وفق التوقيت العالمي UTC إلى القالب الرئيسي، وبعدها سنعرض قيمة هذا المتغير في القالب.
سنترك خادم التطوير قيد التشغيل وسنفتح الملف app.py في نافذة أسطر أوامر جديدة للتعديل عليه:
(env) user@localhost:$ nano app.py
سنستورد الوحدة الخاصّة بالتعامل مع الوقت والتاريخ datetime
من مكتبة بايثون المعيارية وسنعدّل الدالة ()index
ليبدو الملف كما يلي:
import datetime from flask import Flask, render_template app = Flask(__name__) @app.route('/') def hello(): return render_template('index.html', utc_dt=datetime.datetime.utcnow())
نحفظ الملف ونغلقه.
استوردنا في الشيفرة السابقة الوحدة datetime
ومرّرنا المتغير المُسمّى utc_dt
مع قيمة ()datetime.datetime.utcnow
التي تمثّل الوقت والتاريخ الحالي وفق التوقيت العالمي إلى القالب "index.html".
ولعرض قيمة هذا المتغير في الصفحة الرئيسية، سنفتح الملف index.html من أجل التعديل عليه:
(env) user@localhost:$ nano templates/index.html
وسنعدّل الملف كما يلي:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>FlaskApp</title> </head> <body> <h1>Hello World!</h1> <h2>Welcome to FlaskApp!</h2> <h3>{{ utc_dt }}</h3> </body> </html>
نحفظ الملف ونغلقه.
أضفنا موضعًا لنص منسّق مثل عنوان من المستوى الثالث H3
مع استخدام المُحدّد الخاص "{{ … }}" لطباعة قيمة المتغيّر utc_dt
ضمنه.
الآن، بالذهاب إلى صفحة التطبيق الرئيسية ضمن المتصفح:
http://127.0.0.1:5000/
ستظهر صفحة مشابهة للصورة التالية:
ومع نهاية هذه الخطوة نكون قد أنشأنا صفحةً رئيسيةً ذات قالب HTML داخل تطبيق فلاسك، وصيّرنا محتويات قالب آخر، ومرّرنا وأظهرنا قيمة مُتغير، وفيما يلي سنتجنّب تكرار الشيفرات البرمجية باستخدام مفهوم وراثة القوالب.
الخطوة الثانية – استخدام مفهوم وراثة القوالب
سننشئ في هذه الخطوة قالبًا أساسيًا ذا محتوًى قابل للمشاركة مع القوالب الأُخرى، وسنعدّل قالب الصفحة الرئيسية index template للتطبيق ليرث من القالب الأساسي هذا، ثمّ سننشئ صفحةً جديدةً مسؤولةً عن عرض معلومات التطبيق للمستخدمين.
يحتوي القالب الأساسي على أهم مكونات HTML المُستخدمة عادةً في أي قالب، مثل عنوان التطبيق وأشرطة التنقّل والتذييلات.
بدايةً، سننشئ ملفًا جديدًا باسم "base.html" ضمن مجلد القوالب templates:
(env) user@localhost:$ nano templates/base.html
وسنكتب ضمنه الشيفرات التالية:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>{% block title %} {% endblock %} - FlaskApp</title> <style> nav a { color: #d64161; font-size: 3em; margin-left: 50px; text-decoration: none; } </style> </head> <body> <nav> <a href="#">FlaskApp</a> <a href="#">About</a> </nav> <hr> <div class="content"> {% block content %} {% endblock %} </div> </body> </html>
نحفظ الملف ونغلقه.
الجزء الأكبر من الشيفرة السابقة هو تعليمات HTML معيارية تشمل عنوانًا وبعض التنسيقات لروابط التنقل، إضافةً إلى شريط تنقّل مع رابطين، أحدهما للوصول إلى الصفحة الرئيسية للتطبيق والآخر للوصول إلى صفحة المعلومات التي لم ننشئها بعد، إضافةً إلى عنصر الحافظة <div>
ليتضمّن محتويات الصفحة. لا تعمل هذه الروابط بعد لكننا سنوضح في الخطوة التالية كيفية إنشاء الربط بين الصفحات.
أمّا الأجزاء من الشيفرة الموضحّة فيما يلي فهي خاصةٌ بمحرك القوالب جينجا:
-
{% block title %} {% endblock %}
: كتلة برمجية تحجز مكانًا لعنوان الصفحة، الذي سنستخدمه لاحقًا في تحديد العنوان الخاص بكل صفحة في التطبيق دون الحاجة إلى إعادة كتابة قسم الترويسة<head>
كاملًا في كل مرةٍ من أجل كل صفحة. -
{% block content %} {% endblock %}
: كتلة برمجية أخرى ستُستبدل لاحقًا بالمحتوى الفعلي بالاعتماد على القالب الابن، أي القالب الذي يرث شيفرات القالب الرئيسي "base.html".
والآن، بعد أن أصبح لديك قالبٌ رئيسي، يمكنك الاستفادة من ميزاته باستخدام فكرة الوراثة، لذلك افتح الملف "index.html":
(env) user@localhost:$ nano templates/index.html
ثمّ استبدل محتوياته بما يلي:
{% extends 'base.html' %} {% block content %} <h1>{% block title %} Index {% endblock %}</h1> <h1>Hello World!</h1> <h2>Welcome to FlaskApp!</h2> <h3>{{ utc_dt }}</h3> {% endblock %}
استخدمنا في هذه النسخة الجديدة من القالب template.html الوسم {% extends %}
للوراثة من القالب الرئيسي base.html، وذلك عن طريق استبدال الشيفرة السابقة بكتلة المحتوى content
في القالب الرئيسي.
تحوي كتلة المحتوى هذه على وسم <h1>
وبداخله كتلة عنوان title
تحتوي على العبارة "Index"، والتي بدورها تستبدل كتلة العنوان title
الموجودة أصلًا في القالب الرئيسي base.html لتحتوي على العبارة "Index" وبذلك يصبح العنوان كاملًا "Index - FlaskApp"، وبهذه الطريقة نتفادى تكرار نفس النص مرتين، ذلك لأنّ هذا النص سيظهر في عنوان الصفحة وضمن الوسم <h1>
أسفل شريط التصفح الموروث من القالب الرئيسي.
وبذلك يصبح لدينا عدة عناوين، الأوّل بتنسيق من المستوى الأوّل <h1>
يتضمّن النص "!Hello World"، والثاني بتنسيق من المستوى الثاني <h2>
، والثالث بتنسيق من المستوى الثالث <h3>
والذي يحتوي على قيمة متغير الوقت والتاريخ utc_dt
.
تمكنّنا وراثة القوالب من إعادة استخدام شيفرة HTML الموجودة في القوالب الأخرى (القالب الرئيسي base.html في حالتنا) دون الحاجة لتكراره في كل مرة.
اِحفظ الملف وأغلقه، ثم حدِّث الصفحة الرئيسية index في المتصفح، فستظهر الصفحة بشريط تصفح وعنوانٍ منسقٍ كما يلي:
أمّا الآن فسننشئ صفحة معلومات التطبيق، لذا سنفتح الملف "app.py" ولنضيف ضمنه وجهةً جديدةً:
(env) user@localhost:$ nano app.py
ونضيف الوجهة التالية إلى نهايته:
# ... @app.route('/about/') def about(): return render_template('about.html')
استخدمنا المزخرف ()app.route
لإنشاء دالة فلاسك باسم "()about"، إذ ترجع ضمنها نتيجة استدعاء الدالة ()render_template
لدى تمرير اسم ملف القالب "about.html" وسيطًا لها.
نحفظ الملف ونغلقه.
الآن سننشئ ملف قالب باسم "about.html" كما يلي:
(env) user@localhost:$ nano templates/about.html
ونضيف ضمنه الشيفرة التالية:
{% extends 'base.html' %} {% block content %} <h1>{% block title %} About {% endblock %}</h1> <h3>FlaskApp is a Flask web application written in Python.</h3> {% endblock %}
استخدمنا في الشيفرة السابقة الوسم extend
لوراثة الشيفرات من القالب الرئيسي، كما استبدلنا كتلة المحتوى content
في القالب الرئيسي بوسم من النوع <h1>
الذي يعمل أيضًا بمثابة عنوان للصفحة، وأضفنا بعض المعلومات حول التطبيق ضمن وسم من النوع <h3>
.
نحفظ الملف ونغلقه.
بعد التأكد من أنّ خادم التطوير ما يزال قيد التشغيل، ننتقل إلى الرابط التالي باستخدام المتصفح:
http://127.0.0.1:5000/about
فتظهر صفحةٌ مشابهة للصورة التالية:
نلاحظ وراثة شريط التنقل وجزءٍ من العنوان من القالب الأساسي.
ومع نهاية هذه الخطوة نكون قد أنشأنا قالبًا أساسيًا واستخدمناه في بناء صفحة التطبيق الرئيسية وصفحة معلومات التطبيق لتجنُّب تكرار الشيفرات، إلّا أنّ الروابط في شريط التنقل ما تزال غير مُفعّلة ولا تؤدي أية مهمة حتى الآن، لذا سنربط في الخطوة سنربط بين الوجهات في القوالب من خلال تعديل الروابط في شريط التنقل.
الخطوة الثالثة – الربط بين الصفحات
سنتعلّم في هذه الخطوة كيفيّة الربط بين الصفحات باستخدام الدالة المساعدة ()url_for
، إذ سنضيف رابطين إلى شريط التنقل في القالب الأساسي، أحدهما للوصول إلى الصفحة الرئيسية والآخر للوصول إلى صفحة معلومات التطبيق.
بدايةً سنفتح القالب الأساسي لتعديله:
(env) user@localhost:$ nano templates/base.html
ونعدّل الملف كما يلي:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>{% block title %} {% endblock %} - FlaskApp</title> <style> nav a { color: #d64161; font-size: 3em; margin-left: 50px; text-decoration: none; } </style> </head> <body> <nav> <a href="{{ url_for('hello') }}">FlaskApp</a> <a href="{{ url_for('about') }}">About</a> </nav> <hr> <div class="content"> {% block content %} {% endblock %} </div> </body> </html>
استخدمنا في الشيفرة السابقة دالة ()url_for
خاصّة، والتي ستُعيد الرابط الموافق لدالة فلاسك المُمرّرة لها، إذ أنّ الرابط الأول مرتبط بالوجهة الخاصة بدالة فلاسك ()hello
(وهي الصفحة الرئيسية للتطبيق)، في حين يرتبط الرابط الثاني بالوجهة الخاصة بدالة فلاسك ()about
، مع ملاحظة أنّنا مرّرنا اسم دالة فلاسك وسيطًا وليس اسم الوجهة (/
أو about/
).
يساعد استخدام الدالة ()url_for
لبناء الروابط على إدارتها على نحوٍ أفضل، فلو كتبنا الروابط بصورةٍمحددة وبتعليمات برمجية ثابتة فإنها ستفشل مع أول تعديل على الوجهات، في حين أنّه ومع استخدام الدالة ()url_for
فمن الممكن التعديل على الوجهات مع ضمان محافظة الروابط على عملها الطبيعي المتوقع، ناهيك عن كون دالة ()url_for
مسؤولة أيضًا عن أمور أُخرى مثل عزل المحارف الخاصة.
نحفظ الملف ونغلقه.
سنعود الآن إلى الصفحة الرئيسية ونجرّب الروابط الموجودة في شريط التنقل، فنجد أنها تعمل كما هو مطلوب.
ومع نهاية هذه الخطوة نكون قد تعلمنا كيفية استخدام الدالة ()url_for
للربط بين الوجهات في القوالب، وفيما يلي سنضيف بعض العبارات الشرطية للتحكم بما يُعرض في القوالب بناءً على الشروط التي نضعها، كما سنستخدم حلقات "for" التكرارية في قوالبنا لعرض عناصر القائمة.
الخطوة الرابعة - استخدام العبارات الشرطية والحلقات التكرارية
سنستخدم في هذه الخطوة العبارة الشرطية "if" في القوالب للتحكم بما سيُعرض بناءً على شروط محدّدة، كما سنستخدم حلقات "for" التكرارية للتنقّل بين عناصر قوائم بايثون وعرضها، ثمّ سنضيف صفحةً جديدةً إلى التطبيق مهمتها عرض التعليقات ضمن قائمة، بحيث تكون التعليقات ذات رقم الدليل الزوجي بخلفية زرقاء والتعليقات ذات رقم الدليل الفردي بخلفية رمادية.
سننشئ بدايةً وجهةً جديدةً لصفحة التعليقات، لذا سنفتح الملف app.py للتعديل عليه:
(env) user@localhost:$ nano app.py
ونضيف الوجهة التالية إلى نهاية الملف:
# ... @app.route('/comments/') def comments(): comments = ['This is the first comment.', 'This is the second comment.', 'This is the third comment.', 'This is the fourth comment.' ] return render_template('comments.html', comments=comments)
لدينا في الوجهة السابقة قائمة مبنية في بايثون باسم comments
تحتوي على أربعة عناصر (في التطبيق العملي يمكن الحصول على هذه التعليقات من قاعدة البيانات بدلاً من كتابتها يدويًا وتثبيتها ضمن الشيفرة)، لتكون القيمة المعادة هي تصيّير لملف قالب باسم "comments.html" وذلك في السطر الأخير من الشيفرة، ممررين المتغير comments
الذي يحتوي القائمة مثل وسيط إلى ملف القالب.
نحفظ الملف ونغلقه.
بعدها ننشئ ملف جديد باسم comments.html ضمن مجلد القوالب templates للتعديل عليه:
(env) user@localhost:$ nano templates/comments.html
ونضيف ضمنه الشيفرة التالية:
{% extends 'base.html' %} {% block content %} <h1>{% block title %} Comments {% endblock %}</h1> <div style="width: 50%; margin: auto"> {% for comment in comments %} <div style="padding: 10px; background-color: #EEE; margin: 20px"> <p style="font-size: 24px">{{ comment }}</p> </div> {% endfor %} </div> {% endblock %}
وفيه توسعنا بالقالب الأساسي base.html واستبدلنا محتويات كتلة المحتوى content
. واستخدمنا تنسيق عنوان من النمط <h1>
الذي سيعمل أيضًا بمثابة عنوانٍ للصفحة.
كما استخدما حلقة for
التكرارية وهي من ضمن تعليمات محرّك القوالب جينجا وذلك في السطر البرمجي {% for comment in comments %}
بهدف التنقل بين التعليقات في القائمة comments
المُخزنة ضمن المتغير المسمّى comment
، إذ سنعرض التعليقات باستخدام الوسم <p style="font-size: 24px">{{ comment }}</p>
، أي بنفس الآلية التي يُعرض فيها أي متغير في جينجا، مع ملاحظة أنّ نهاية حلقة for
هنا تكون باستخدام الكلمة المفتاحية {% endfor %}
الأمر المختلف عن طريقة بناء حلقات for
في بايثون.
نحفظ الملف ونغلقه.
الآن وبعد التأكد من كون خادم التطوير ما يزال قيد التشغيل، نفتح المتصفح وننتقل إلى صفحة التعليقات باستخدام الرابط:
http://127.0.0.1:5000/comments
فتظهر الصفحة بما يشبه التالي:
الآن سنستخدم عبارة if
الشرطية في القوالب لعرض التعليقات ذات رقم الدليل الفردي بخلفية رمادية والتعليقات ذات رقم الدليل الزوجي بخلفية زرقاء.
لذا سنفتح ملف القالب comments.html:
(env) user@localhost:$ nano templates/comments.html
ثمّ سنعدله ليصبح كما يلي:
{% extends 'base.html' %} {% block content %} <h1>{% block title %} Comments {% endblock %}</h1> <div style="width: 50%; margin: auto"> {% for comment in comments %} {% if loop.index % 2 == 0 %} {% set bg_color = '#e6f9ff' %} {% else %} {% set bg_color = '#eee' %} {% endif %} <div style="padding: 10px; background-color: {{ bg_color }}; margin: 20px"> <p>#{{ loop.index }}</p> <p style="font-size: 24px">{{ comment }}</p> </div> {% endfor %} </div> {% endblock %}
وفي هذا التعديل أضفنا عبارة if
الشرطية في السطر {% if loop.index % 2 == 0 %}
، إذ أنّ المتغير loop
هو متغير خاص في جينجا يمكنّنا من الوصول إلى معلومات حول الحلقة الحالية، واستخدمنا loop.index
للحصول على دليل العنصر الحالي، الذي يبدأ من "1" وليس من "0" كما هو الحال في قوائم بايثون.
تتحقق عبارة if
ما إذا كان الدليل زوجياً باستخدام عملية باقي القسمة %
، إذ تُفحص قيمة باقي قسمة الدليل على الرقم "2"، فإذا كان الباقي يساوي "0"، فهذا يعني أن الدليل زوجي، وإلّا فيكون الدليل فرديًا. استخدمنا الوسم {% set %}
للتعريف عن متغير باسم bg_color
ليتضمّن لون الخلفية المطلوب، فإذا كان الدليل زوجيًا، نضبط قيمة هذا المتغير إلى اللون الأزرق، وإلّا وفي حال كون الدليل فرديًا نضبطها إلى اللون الرمادي، ثم نخصّص المتغير bg_color
لضبط لون الخلفية للوسم <div>
الحاوي على التعليق. نستخدم loop.index
في أعلى النص الخاص بالتعليق لعرض رقم الدليل الحالي ضمن وسمٍ من النوع <p>
.
نحفظ الملف ونغلقه.
الآن بفتح المتصفح والانتقال إلى صفحة التعليقات كما يلي:
http://127.0.0.1:5000/comments
ستظهر صفحة التعليقات الجديدة كما في الصورة:
وضّحنا فيما سبق كيفية استخدام العبارة الشرطية if
، ولكن من الممكن الحصول على نفس النتيجة باستخدام الدالة الخاصّة المساعدة ()loop.cycle
في جينجا، ولتوضيح هذه النقطة سنفتح الملف "comments.html":
(env) user@localhost:$ nano templates/comments.html
ونعدله ليصبح كما يلي:
{% extends 'base.html' %} {% block content %} <h1>{% block title %} Comments {% endblock %}</h1> <div style="width: 50%; margin: auto"> {% for comment in comments %} <div style="padding: 10px; background-color: {{ loop.cycle('#EEE', '#e6f9ff') }}; margin: 20px"> <p>#{{ loop.index }}</p> <p style="font-size: 24px">{{ comment }}</p> </div> {% endfor %} </div> {% endblock %}
حذفنا في هذه الشيفرة العبارة الشرطية "if/else" واستخدمنا عوضًا عنها الدالة المساعدة ('loop.cycle('#EEE', '#e6f9ff
للتنقل بين اللونين، إذ ستكون قيمة المتغيّر background-color
مساويةً للقيمة "#EEE" للون الرمادي مرّةً، ثمّ تتحول إلى القيمة "#e6f9ff" للون الأزرق مرةً أُخرى، وهكذا بالتناوب.
نحفظ الملف ونغلقه.
لدى فتح صفحة التعليقات في المتصفح وتحديثها، ستظهر صفحةٌ مشابهةٌ لتلك التي ظهرت مع استخدام عبارة "if" الشرطية.
يمكن استخدام عبارات "if" الشرطية لأغراض مختلفة، بما في ذلك التحكم بما يُعرض على الصفحة، فعلى سبيل المثال لعرض جميع التعليقات باستثناء التعليق الثاني، يمكننا استخدام عبارة if
بالشرط loop.index != 2
لترشيح التعليق الثاني.
لذا سنفتح قالب التعليقات:
(env) user@localhost:$ nano templates/comments.html
ونعدله ليصبح كما يلي:
{% extends 'base.html' %} {% block content %} <h1>{% block title %} Comments {% endblock %}</h1> <div style="width: 50%; margin: auto"> {% for comment in comments %} {% if loop.index != 2 %} <div style="padding: 10px; background-color: #EEE; margin: 20px"> <p>#{{ loop.index }}</p> <p style="font-size: 24px">{{ comment }}</p> </div> {% endif %} {% endfor %} </div> {% endblock %}
استخدمنا العبارة الشرطية {% if loop.index != 2 %}
لعرض التعليقات ذات رقم الدليل غير المساوي للرقم "2"، ما يشمل جميع التعليقات باستثناء التعليق الثاني، كما استخدمنا شيفرةً ثابتةً لتغيير لون الخلفية بدلًا من استخدام الدالة المساعدة ()loop.cycle
لتسهيل الأمور، أمّا بالنسبة لبقية أجزاء الشيفرة، فلم نجري عليها أي تغييرات، وأيضًا هنا تُنهى عبارة if
الشرطية باستخدام الأمر {% endif %}
.
نحفظ الملف ونغلقه.
سنلاحظ بتحديث صفحة التعليقات عدم ظهور التعليق الثاني.
سنضيف الآن رابط يسمح للمستخدم بالانتقال إلى صفحة التعليقات مباشرةً من شريط التنقل، لذا سنفتح القالب الأساسي للتعديل عليه:
(env) user@localhost:$ nano templates/base.html
وسنعدّل محتويات الوسم <nav>
بإضافة رابط جديد <a>
إليه كما يلي:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>{% block title %} {% endblock %} - FlaskApp</title> <style> nav a { color: #d64161; font-size: 3em; margin-left: 50px; text-decoration: none; } </style> </head> <body> <nav> <a href="{{ url_for('hello') }}">FlaskApp</a> <a href="{{ url_for('comments') }}">Comments</a> <a href="{{ url_for('about') }}">About</a> </nav> <hr> <div class="content"> {% block content %} {% endblock %} </div> </body> </html>
استخدمنا الدالة المساعدة ()url_for
للوصول إلى دالة فلاسك ()comments
نحفظ الملف ونغلقه.
وبذلك أصبح شريط التنقل يحتوي على رابط جديد ينقل المستخدم إلى صفحة التعليقات مباشرةً.
وبذلك نكون قد تعلمنا كيفية استخدام العبارة الشرطية "if" في القوالب للتحكّم بما يُعرَض وفقًا لشروط معينة، كما استخدمنا حلقات "for" التكرارية للتنقل بين عناصر قوائم بايثون وعرض كل منها، وتعلمنا كيفية استخدام المتغير "loop" الخاص في جينجا، وسنستخدم في الخطوة التالية مرشّحات جينجا للتحكّم بكيفية عرض بيانات المتغير.
الخطوة 5 – استخدام المرشحات
سنتعلّم في هذه الخطوة كيفيّة استخدام مُرشحات جينجا في القوالب، إذ سنستخدم المُرشّح upper
لتحويل كامل نصوص التعليقات التي أضفناها في الخطوة السابقة (أو الواردة من قاعدة البيانات في الواقع العملي) إلى حالة أحرف كبيرة، في حين سنستخدم المرشح join
لربط عدّة سلاسل نصيّة لتصبح سلسلةً واحدة، كما سنتعرّف على كيفيّة تصيّير شيفرة HTML موثوقة وآمنة دون الحاجة لعزلها باستخدام المرشح safe
.
بدايةً سنعمل على تحويل التعليقات في صفحة التعليقات إلى حالة الأحرف الكبيرة، لذا سنفتح القالب "comments.html" للتعديل عليه:
(env) user@localhost:$ nano templates/comments.html
ثمّ سنعدله ليصبح كما يلي:
{% extends 'base.html' %} {% block content %} <h1>{% block title %} Comments {% endblock %}</h1> <div style="width: 50%; margin: auto"> {% for comment in comments %} {% if loop.index != 2 %} <div style="padding: 10px; background-color: #EEE; margin: 20px"> <p>#{{ loop.index }}</p> <p style="font-size: 24px">{{ comment | upper }}</p> </div> {% endif %} {% endfor %} </div> {% endblock %}
أضفنا مرشح upper
باستخدام رمز |
الذي يمثل أنبوبًا pipe، والذي سيعمل على تعديل قيمة متغير comment
إلى حالة الأحرف الكبيرة.
نحفظ الملف ونغلقه.
وبعد التأكد من كون خادم التطوير ما يزال قيد التشغيل، ننتقل إلى صفحة التعليقات باستخدام المتصفح:
http://127.0.0.1:5000/comments
ونلاحظ أنّ جميع التعليقات أصبحت في حالة الأحرف الكبيرة بعد تطبيق المرشح، كما من الممكن تمرير وسطاء للمُرشّحات بين أقواسها. لتوضيح هذه النقطة: سنستخدم المُرشّح join
لدمج جميع التعليقات الموجودة في القائمة comments
ضمن سلسلة واحدة.
لذا سنفتح قالب التعليقات:
(env) user@localhost:$ nano templates/comments.html
ونعدله كما يلي:
{% extends 'base.html' %} {% block content %} <h1>{% block title %} Comments {% endblock %}</h1> <div style="width: 50%; margin: auto"> {% for comment in comments %} {% if loop.index != 2 %} <div style="padding: 10px; background-color: #EEE; margin: 20px"> <p>#{{ loop.index }}</p> <p style="font-size: 24px">{{ comment | upper }}</p> </div> {% endif %} {% endfor %} <hr> <div> <p>{{ comments | join(" | ") }}</p> </div> </div> {% endblock %}
أضفنا في الشيفرة السابقة الوسم <hr>
(الذي يُصيّر مثل فاصل أفقي)، والوسم <div>
(الذي يُستخدم لأغراض التنسيق وتجميع العناصر)، إذ دمجنا جميع التعليقات الموجودة في القائمة comments
باستخدام المُرشّح ()join
.
نحفظ الملف ونغلقه.
وبتحديث صفحة التعليقات ستظهر كما يلي:
نلاحظ عرض كافّة محتويات القائمة comments
بحيث تكون التعليقات مفصولة عن بعضها برمز الشريط العمودي pipe وهي القيمة المُمررة وسيطًا إلى المُرشّح ()join
.
ومن المُرشّحات المهمّة الأُخرى المُرشّح safe
الذي يساعد في تصيّير شيفرة HTML موثوقة في المتصفح، ولتوضيح ذلك سنضيف نصًا يحتوي على وسم HTML ما إلى قالب التعليقات (لنرى هل سيُصيّر في المُتصفّح أم سيُعامل على أنه نص عادي) باستخدام مُحدّد جينجا "{{ }}". نحصل في التطبيق العملي على هذا النص (التعليق) مثل متغير آتٍ من الخادم، ومن ثمّ سنعدّل وسيط المُرشّح ()join
ليكون وسم <hr>
(أي الفصل بين التعليقات بشريط أفقي) بدلًا من الشريط العمودي pipe.
لذا سنفتح قالب التعليقات:
(env) user@localhost:$ nano templates/comments.html
ونعدّله ليصبح كما يلي:
{% extends 'base.html' %} {% block content %} <h1>{% block title %} Comments {% endblock %}</h1> <div style="width: 50%; margin: auto"> {% for comment in comments %} {% if loop.index != 2 %} <div style="padding: 10px; background-color: #EEE; margin: 20px"> <p>#{{ loop.index }}</p> <p style="font-size: 24px">{{ comment | upper }}</p> </div> {% endif %} {% endfor %} <hr> <div> {{ "<h1>COMMENTS</h1>" }} <p>{{ comments | join(" <hr> ") }}</p> </div> </div> {% endblock %}
أضفنا القيمة <h1>COMMENTS</h1>
وغيرنا وسيط مُرشّح الدمج ليصبح وسم <hr>
.
نحفظ الملف ونغلقه.
وبتحديث صفحة التعليقات تظهر صفحةٌ مشابهةٌ لما يلي:
نلاحظ أنّ وسوم HTML التي أضفناها ضمن التعليقات لم تُصيَّر، وهي ميزة أمان مهمّة في جينجا، لأنّ وسوم HTML قد تكون ضارة أو خبيثة، ويمكن أن تؤدي إلى هجمات البرمجة العابرة للمواقع XSS.
أمّا لتصيير وسوم HTML المُضافة السابقة نفتح ملف قالب التعليقات:
(env) user@localhost:$ nano templates/comments.html
ونعدله بإضافة المُرشّح safe
، للدلالة على أنّ هذه الوسوم آمنة ونريد تصييّرها:
{% extends 'base.html' %} {% block content %} <h1>{% block title %} Comments {% endblock %}</h1> <div style="width: 50%; margin: auto"> {% for comment in comments %} {% if loop.index != 2 %} <div style="padding: 10px; background-color: #EEE; margin: 20px"> <p>#{{ loop.index }}</p> <p style="font-size: 24px">{{ comment | upper }}</p> </div> {% endif %} {% endfor %} <hr> <div> {{ "<h1>COMMENTS</h1>" | safe }} <p>{{ comments | join(" <hr> ") | safe }}</p> </div> </div> {% endblock %}
نلاحظ أنّه من الممكن ربط المُرشّحات كما هو ظاهر في السطر:
<p>{{ comments | join(" <hr> ") | safe }}</p>
إذ يُطبَّق كل مُرشّح على نتيجة خرج المُرشّح السابق له.
نحفظ الملف ونغلقه.
وبتحديث صفحة التعليقات نجد أن وسوم HTML قد جرى تصييّرها كما هو مُتوقّع مع استخدام المُرشّح "safe":
تنبيه: قد يجعل تطبيق المُرشّح "safe" لشيفرة HTML غير موثوقة المصدر تطبيقنا عرضةً لهجمات البرمجة عابرة المواقع، لذا لا يُفضّل استخدامه إلّا إذا كانت شيفرات HTML المُراد تصييّرها موثوقة المصدر.
تعرّفنا في هذه الخطوة على كيفيّة استخدام المُرشّحات في قوالب جينجا لتعديل قيم المتغيرات، وفيما يلي سنستخدم حزمة بوتستراب Bootstrap لتنسيق مظهر التطبيق وجعله أكثر جاذبية بصريًا.
الخطوة السادسة – استخدام بوتستراب
سنتعرّف في هذه الخطوة على كيفيّة استخدام حزمة بوتستراب لتنسيق مظهر التطبيق، إذ سنضيف شريط تنقّل في القالب الأساسي بحيث يظهر في كافّة الصفحات التي ترث شيفراتها منه.
يساعد إطار العمل بوتستراب bootstrap على إضافة التنسيقات على التطبيق ليبدو أكثر جاذبية بصريًا، كما سيساعدنا على تمكين ميزة الصفحات المتوافقة مع المتصفحات في تطبيق الويب، ما يضمن عمله بصورةٍ جيدة في المتصفحات الخاصة بالجوال، دون كتابة شيفرات HTML و CSS وجافا سكربت JavaScript لتحقيق هذه الغاية.
وحتّى نستخدم بوتستراب سنضيفه إلى القالب الأساسي بحيث يسهل استخدامه في جميع القوالب الأُخرى.
لذا سنفتح القالب الأساسي "base.html" للتعديل عليه:
(env) user@localhost:$ nano templates/base.html
ونعدله كما يلي:
<!doctype html> <html lang="en"> <head> <!-- Required meta tags --> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <!-- Bootstrap CSS --> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-KyZXEAg3QhqLMpG8r+8fhAXLRk2vvoC2f3B09zVXn8CA5QIVfZOJ3BCsw2P0p/We" crossorigin="anonymous"> <title>{% block title %} {% endblock %} - FlaskApp</title> </head> <body> <nav class="navbar navbar-expand-lg navbar-light bg-light"> <div class="container-fluid"> <a class="navbar-brand" href="{{ url_for('hello') }}">FlaskApp</a> <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation"> <span class="navbar-toggler-icon"></span> </button> <div class="collapse navbar-collapse" id="navbarNav"> <ul class="navbar-nav"> <li class="nav-item"> <a class="nav-link" href="{{ url_for('comments') }}">Comments</a> </li> <li class="nav-item"> <a class="nav-link" href="{{ url_for('about') }}">About</a> </li> </ul> </div> </div> </nav> <div class="container"> {% block content %} {% endblock %} </div> <!-- Optional JavaScript --> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/js/bootstrap.bundle.min.js" integrity="sha384-U1DAWAznBHeqEIlVSCgzq+c9gqGAJn5c/t99JyeKa9xxaYpSvHU5awsuZVVFIhvj" crossorigin="anonymous"></script> </body> </html>
الجزء الأكبر من الشيفرة السابقة هو تعليمات لازمة لعمل بوتستراب، إذ تزوّد الوسوم <meta>
متصفح الويب بالمعلومات، في حين ينشئ الوسم <link>
ضمن القسم <head>
ارتباطًا إلى ملفات CSS الخاصة ببوتستراب، وفي الجزء الأخير منه يُضمِّن الوسم <script>
ارتباطًا إلى شيفرة جافا سكربت اختيارية. كما تتضمّن الشيفرة أجزاءً خاصةً بمحرك القوالب جينجا والتي قد وضّحناها فيما سبق، وكذلك استخدامًا لوسوم مُحدّدة وأصناف من CSS لتحديد كيفيّة عرض كل عنصر في بوتستراب.
وقد استخدمنا في الشيفرة أعلاه وسم رابط <a>
من الصنف navbar-brand
الخاص بإضافة رابط العلامة المميزة (شعار الشركة مثلًا) إلى شريط التصفّح وذلك ضمن الوسم <nav>
(الخاص بشريط التصفّح)، وفيه نحدّد رابط العلامة المطلوب، أمّا عن الروابط الاعتيادية مثل الروابط المؤدية إلى صفحات أُخرى من التطبيق، فمن الممكن تضمينها في الوسم <"ul class="navbar-nav>
الحاوي على عنصر قائمة <li>
ضمن عنصر روابط <a>
وبالتالي من الممكن إضافة عدّة روابط.
نحفظ الملف ونغلقه.
وبعد التأكد من كون خادم التطوير ما يزال قيد التشغيل، ننتقل إلى الصفحة الرئيسية من التطبيق في المتصفح:
http://127.0.0.1:5000/
فتظهر صفحةٌ مشابهةٌ لما يلي:
وبذلك أصبح من الممكن استخدام مكونات بوتستراب لتنسيق مظهر العناصر في تطبيق فلاسك في جميع القوالب.
الخاتمة
تعرّفنا في هذا المقال على كيفيّة استخدام قوالب HTML في تطبيق فلاسك، إذ استخدمنا المتغيرات لتمرير البيانات من الخادم إلى القوالب مستفيدين من مفهوم وراثة القوالب لتجنُّب تكرار الشيفرات، كما استخدمنا عناصر مثل عبارات "if" الشرطية وحلقات "for" التكرارية، وربطنا بين صفحات مختلفة من صفحات التطبيق، كما تعرّفنا على كيفية تطبيق المُرشّحات لتعديل نص ما أو لعرض شيفرة HTML موثوقة، ونهايةً استخدمنا بوتستراب لتنسيق مظهر التطبيق.
ترجمة -وبتصرف- للمقال How To Use Templates in a Flask Application لصاحبه Abdelhadi Dyouri.
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.